Pythonのリスト内包表記のアルゴリズム

こちらはPythonのリスト内包表記が100倍楽しくなる記事です。

さて、Pythonを使っていてリスト内包表記が出てくると初めはとても戸惑うかもしれません。

しかし、なんら臆することはありません。よく見てみたらそれはただの見慣れたネスト文なのです。


リスト内包表記とは

リスト内包表記とは、for文やifを使ってある条件のリストのみを取りだしたい場合に使える記述方法のことです。

たとえば、偶数のみの配列をつくりたい場合、人々はズル賢いのでNumpyarange()メソッドを思い浮かぶかもしれませんがこれだとlist型を受け取ることができません。

これを純粋なPythonのみで記述しようとすると一気に知能レベルが低下し

even_numbers = []

for i in range(10):
if i % 2 == 0:
even_numbers.append(i)

とするでしょう。しかし、人間は学習能力を持ちます。もしかしたらPython特有のリスト内包表記の存在に気づくかもしれません。

even_numbers = [number for number in range(10) if number % 2 == 0]

と一行で記述することができます。

Swiftとかにも三項演算子なんかがありますが、Pythonのすごいところはここから。


たとえば、こんな二重for文は

years = [2016, 2017, 2018, 2019]

months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

calendar = []
for year in years:
for month in months:
calendar.append(f'{year}年{month}月')




リスト内包表記だと

calendar = [f'{year}年{month}月' for year in years for month in months]

こんな感じで一行で書けます。

単にappend()を先に書くか後に書くかの違いですがこれは動的な型付け言語だからこそ成り立っています。


リスト内包表記のアルゴリズム

それでは本題に入っていきます。

まず、こんなデータを想定します。

説明のためシンプルにしましたが本来は同一のkeyが複数あることを想像しておいてください。

json_exp.001.jpeg

これをjson形式に表すとこんな感じです

users = {1: {1: "jiro", 2: "goro"}, 2: "aya"}


そして、たとえば、"goro"にあたるvalueを取り出す場合、その取りだしたい"goro"が複数あることを想定すると以下のようにfor文で記述しますね。

user = []

for a, b in users.items():
if a == 1:
for c, d in b.items():
if c == 2:
user.append(d)


一文字変数なんで読みづらいですが抽象化のためなので悪しからず。

こうすると、人々の認識通り、上のforから順番に下に降りてきて"goro"に辿りつきます。

Untitled.001.jpeg

これをリスト内包表記で記述したい場合、

user = [d for a, b in users.items() if a == 1 for c, d in b.items() if c == 2]

となります。本当にappend()が後か先かだけです。

他の記事で


外側のfor文を後ろに書く


という表記をよく見かけますが、これは危険です。

なぜなら結局リスト内包表記もfor文と同じで記述した順に探索していくからです。

特に今回のようなループしたい変数が上から下に降りてくるようなネストでは記述の順番を変えてしまうと定義前となってしまう可能性が高いです。

これをリスト内包表記で書くと

user = [d for c, d in b.items() if c == 2 for a, b in users.items() if a == 1]




当たり前ですがこういったエラーが発生します

'str' object has no attribute 'items'


この場合、データの深度がバラバラなためまずまず探索ができませんというエラーを返します。

しかし、このように深度を揃えたとしても

users = {1: {1: "jiro", 2: "goro"}, 2: {1: "aya"}}

深度を遡って探索することになり、未定義エラーが発生します。

b is not defined

Untitled.001.jpeg

なので、公式ドキュメントにも記載してある通り、リスト内包表記は通常のfor文と同じ順番でappend()のみを先頭に持ってくる感じで書きましょう。

さしづめ後置for文といったところでしょう。


まとめ

つまり、リスト内包表記に通常のfor文とのアルゴリズムの差異はありません。

リスト内包表記が高速だと言われているのは初めから変数に内包しているためと、そうすることでappend()を呼び出す必要がなくなるからでしょう。