かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Python3 要素が n の配列を生成したい

データを突っ込む初期値として [0, 0, 0] とか [[], [], []] みたいな配列を作りたい

* n で要素を繰り返した配列が作れる

data = [0] * 3
# [0, 0, 0]

* n する配列の要素全てが n 回繰り返される

data = [1, 2, 3] * 3
# [1, 2, 3, 1, 2, 3, 1, 2, 3]

要素に配列や辞書を持つリスト作成は注意が必要

* n の繰り返しは同じ要素を繰り返しているので参照値である配列や辞書は同じ参照を持つものが繰り返されてしまうので注意が必要

配列

data = [[]] * 3
# [[], [], []]
data[0].append(1)
# [[1], [1], [1]]

辞書

data = [{}] * 3
# [{}, {}, {}]
data[0]['foo'] = 'bar'
# [{'foo': 'bar'}, {'foo': 'bar'}, {'foo': 'bar'}]
data[0].update({'foo': '1', 'bar': 2})
# [{'foo': '1', 'bar': 2}, {'foo': '1', 'bar': 2}, {'foo': '1', 'bar': 2}]

参照のデータを要素にしたい場合は素直にリスト内表記で作成すればOK

配列

data = [[] for i in range(3)]
# [[], [], []]
data[0].append(100)
# [[100], [], []]

辞書

data = [{} for i in range(3)]
# [{}, {}, {}]
data[0]['foo'] = 'bar'
# [{'foo': 'bar'}, {}, {}]
data[2].update({'foo': '1', 'bar': 2})
# [{'foo': 'bar'}, {}, {'foo': '1', 'bar': 2}]

おまけ: 辞書は * n できない

{'a': 1} * 3
TypeError: unsupported operand type(s) for *: 'dict' and 'int'

所管

簡単に要素が n 個の配列・辞書を作ることが出来ました!
参照なデータを要素に取る時だけ作り方に注意が必要そうです。

思い出したかのように時々触るくらいなので覚えられない!


[参考]

Pythonでつくる ゲーム開発 入門講座

Pythonでつくる ゲーム開発 入門講座

ゲーム作るの楽しそう!

🐍 繋がりだけでサムネにしようと貼った邪神ちゃん

MySQL 1回のクエリで取れたデータを別々の条件別にカウントしたい

SELECT 全体のレコード数, 条件A の総数, 条件B の総数
FROM テーブル
WHERE 条件
GROUP BY カラム

こんな感じで特定の条件でグループ化したデータをグルーピングされた中で、さらに条件別に数を出したい。
例えばユーザーの購入レコードがあって、ユーザー毎の購入総数・カテゴリAの購入数・カテゴリBの購入数を表示したいみたいなケース。

データ

こんな感じの購入履歴のデータ

mysql> select * from purchase_log;
+----+---------+---------+----------+
| id | user_id | item_id | category |
+----+---------+---------+----------+
|  1 |       1 |       1 |        1 |
|  2 |       1 |       2 |        2 |
|  3 |       1 |       3 |        1 |
|  4 |       2 |       1 |        1 |
|  5 |       2 |       2 |        2 |
|  6 |       2 |       4 |        2 |
|  7 |       3 |       2 |        2 |
|  8 |       3 |       3 |        1 |
+----+---------+---------+----------+

user_id ごとの総数とカテゴリー別の購入数を取りたい

総数は user_id 毎にグループ化してレコード数を count すればOK

mysql> SELECT user_id, count(*) FROM purchase_log GROUP BY user_id;
+---------+----------+
| user_id | count(*) |
+---------+----------+
|       1 |        3 |
|       2 |        3 |
|       3 |        2 |
+---------+----------+

count の中で if 文を使って条件付きのレコード数を出力できる

IF(expr1,expr2,expr3)
If expr1 is TRUE (expr1 <> 0 and expr1 <> NULL), IF() returns expr2. Otherwise, it returns expr3.
cf. MySQL :: MySQL 8.0 Reference Manual :: 12.5 Flow Control Functions

count(if(条件, 1, null))

の形で条件にマッチしたレコード数だけが取れる

mysql> SELECT user_id, count(*) as total,
  count(if(category = 1, 1, null)) as 'cat 1',
  count(if(category = 2, 1, null)) as 'cat 2'
FROM purchase_log
GROUP BY user_id;
+---------+-------+-------+-------+
| user_id | total | cat 1 | cat 2 |
+---------+-------+-------+-------+
|       1 |     3 |     2 |     1 |
|       2 |     3 |     1 |     2 |
|       3 |     2 |     1 |     1 |
+---------+-------+-------+-------+

⚠️ if の else のときの条件を 0 にすると正しくカウントされない

count(if(条件, 1, 0))

count なので条件に合わない時は 0 でも良いのかなと思ったが、上記のような方法では 0 も存在するという判断でカウントされるので上手く行かない

mysql> SELECT user_id, count(*) as total,
  count(if(category = 1, 1, 0)) as 'cat 1',
  count(if(category = 2, 1, 0)) as 'cat 2'
FROM purchase_log
GROUP BY user_id;
+---------+-------+-------+-------+
| user_id | total | cat 1 | cat 2 |
+---------+-------+-------+-------+
|       1 |     3 |     3 |     3 |
|       2 |     3 |     3 |     3 |
|       3 |     2 |     2 |     2 |
+---------+-------+-------+-------+

1, 0 にする場合は SUM を使えば正しい値になる。

mysql> SELECT user_id, count(*) as total,
  sum(if(category = 1, 1, 0)) as 'cat 1',
  sum(if(category = 2, 1, 0)) as 'cat 2'
FROM purchase_log
GROUP BY user_id;
+---------+-------+-------+-------+
| user_id | total | cat 1 | cat 2 |
+---------+-------+-------+-------+
|       1 |     3 |     2 |     1 |
|       2 |     3 |     1 |     2 |
|       3 |     2 |     1 |     1 |
+---------+-------+-------+-------+

まとめ

  • COUNT は 0 はカウントされる
  • SUM は数値計算なので 0 はカウントされない
  • COUNT, SUM 共に内部に IF を使って条件設定が可能

[参考]

React 条件分岐のある箇所で Hooks を使うとエラーになる

Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement. というエラーに出会ったのでメモ。

環境
  • React ^17.0.1

エラーが発生した経緯

ロード中と完了後に別のコンポーネントを表示したいとか、状態が違う時に別のコンポーネントを表示したいとか JSX 内に分岐がある箇所に Hooks を使うとエラーになるっぽい。

エラーの再現ができなかったのですが、map でループして表示させているコンポーネントを条件分岐させて、片方で props を整形させる hooks を使っていると上記のエラーになりました。

const Articles: VFC<ArticlesProps> = (props) => {
  const [isEdit, setIsEdit] = useState(false);
  const onChangeMode = useCallback((mode: boolean) => () => {
    setIsEdit(mode);
  }, []);
  
  return isEdit
    ? <ArticleEditForm onEditEnd={onChangeMode(false)} {...props} />
    : <ArticleBody {...useBuildArticleProps(props)} onEdit={onChangeMode(true)}>;
};

cost App: VFC = () => {
  const { isLoading, data } = useLoadArticles();

  return (
    <div>
      {isLoading ? <Loading /> : <ul>{data.map((item) => <Articles {...item} />)}</ul>}
    </div>
  );
};

=> Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

条件によって Hooks の数が変わってしまう事が原因なようで、useBuildArticleProps を条件式で上の JSX ではなく、呼び出したコンポーネント内に移動させればエラーにはならなくなりました。

// 別のコンポーネントにしてその中から Hooks  を呼び出せばOK
const ArticleContainer: VFV<ArticleContainerProps> = ({onEdit, ...props}) => {
  return <ArticleBody {...useBuildArticleProps(props)} onEdit={onChangeMode(true)}>;
};

const Articles: VFC<ArticlesProps> = (props) => {
  const [isEdit, setIsEdit] = useState(false);
  const onChangeMode = useCallback((mode: boolean) => () => {
    setIsEdit(mode);
  }, []);
  
  return isEdit
    ? <ArticleEditForm onEditEnd={onChangeMode(false)} {...props} />
    : <ArticleContainer onEdit={onChangeMode(true)} {…props}>;
};

ESLint で eslint-plugin-react-hooks を使っていると、条件付きで Hooks を呼び出す箇所をエラーにしてくれる

cost App: VFC = () => {
  const { isLoading, data } = useLoadArticles();

  return (
    <div>
      {isLoading ? <Loading /> : <Articles {…useBuildProps(data)} />}
    </div>
  );
};

=> React Hook "useBuildProps" is called conditionally. React Hooks must be called in the exact same order in every component render
※ ESLint で eslint-plugin-react-hooks を導入してない場合このエラーは表示されない

useBuildProps を条件分岐で呼び出した別のコンポーネント内で実行するようにすればOK

const ArticleContainer: VFC<ArticleContainerProps> = ({ data }) => {
  return <Article {…useBuildProps(data)} />;
};

cost App: VFC = () => {
  const { isLoading, data } = useLoadArticles();

  return (
    <div>
      {isLoading ? <Loading /> : <ArticleContainer data=(data) />}
    </div>
  );
};

条件分岐したコンポーネントで Hooks が呼ばれてるから同じなのでは?と感じてしまうのですが React 的には別のコンポーネント内に閉じていると問題ないっぽい。

所管

ESLint の eslint-plugin-react-hooks プラグインを使っていれば、条件分岐の JSX 内で Hooks を呼び出しているとエラーを出してくれるので事前に回避することができそうです。


[参考]

はじめてつくるNext.jsサイト

はじめてつくるNext.jsサイト

実践TypeScript ~	BFFとNext.js&Nuxt.jsの型定義~

実践TypeScript ~ BFFとNext.js&Nuxt.jsの型定義~

  • 作者:吉井 健文
  • 発売日: 2019/06/26
  • メディア: 単行本(ソフトカバー)

そろそろ Next.js 手を出していく…