接着剤の精進日記

競プロでの精進や研究に関係したことを書いていきます。

Python リスト内包表記

リスト内包表記について

リスト内包表記はPythonらしい書き方である。
言葉で表現すると、
ひとつ以上のイテレータからPythonデータ構造をコンパクトに作れる形式*1である。
内包表記を使えば、ループなどを簡潔に書けるし、この書き方のほうが高速。
詳しく知りたい人は以下の参考ページへ。
dsas.blog.klab.org

具体例

言葉だけで説明されてもよくわからないと思うので実際に見てみよう。

a = [i for i in range(5)]
print(a)
>[0, 1, 2, 3, 4]

といった感じになる。
これを内包表記を使わずに書くと、

a = []
for i in range(5):
    a.append(i)
print(a)
>[0, 1, 2, 3, 4]

と結果は同様のものとなる。
変数iにfor文が後置修飾のような形で、0,1,...,4と生成したものを順にiに渡していっている。
なので、for文を先に1つ処理し、その結果を前の変数に渡している感じと言えばだろうか。
あとは、上記の処理を終わるまで続ける。処理が終わると例のようなリストが完成する。

内包表記での条件演算子

例えば、1~10までの数の中で偶数のリストを作りたいとする。
それを普通に書くと以下のようになる。

a = []
for i in range(1,11):
     if i % 2 == 0:
             a.append(i)
print(a)
>[2, 4, 6, 8, 10]

内包表記を使うと以下のようになり、かなりスッキリと書くことができ、Pythonらしいコードとなる。

a = [i for i in range(1,11) if i % 2 == 0]
print(a)
>[2, 4, 6, 8, 10]

先程の例のようにi = 1,...,10までを一つずつ処理していく、
そしてi = 1のとき、if 1 % 2 == 0 はFalseなのでリストには入らない
i = 2のとき、if 2 % 2 == 0はTrueなのでリストに入れる
以下ループの終わりまで繰り返すといったような処理が行われる。
for文でiに値を入れ、その後if文にて判定を行うというような処理が行われる。

以下のように書くことで1~5までの二乗のリストを作るといったようなこともできる。

a = [i ** 2 for i in range(1, 6)]
print(a)
>[1, 4, 9, 16, 25]

内包表記での二重ループ

これまでは、内包表記でのfor文ループの書き方を記してきたが、
内包表記での二重ループももちろん書ける。具体的には以下のようなものとなる。

a = [(i, j)
       for i in range(5)
       for j in range(5)]
print(a)
>[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4),
  (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), 
  (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), 
  (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), 
  (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)]

通常の二重ループと同様にまずi = 0が生成され、
その後、内側のループに入り、j = 0,...,4と順に処理される。
内側のループが終わると、外側に戻り、i = 1となりループが終わるまで同様に繰り返す。


また、内包表記の二重ループを使って、
リストの中のリストを作ることも可能だ。

a = [[(i, j)
       for i in range(5)]
       for j in range(5)]
print(a)
>
[[(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)], 
[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1)], 
[(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)], 
[(0, 3), (1, 3), (2, 3), (3, 3), (4, 3)],
[(0, 4), (1, 4), (2, 4), (3, 4), (4, 4)]]

ただし、この書き方だと、for i in range(5)が内側のループで
for j in range(5)が外側のループとなってしまうみたいだ。
これは、[A for j in range(5)]と解釈され、先に j = 0となる。
その結果をAに渡し、Aである[(i, j) for i in range(5)]が処理されるため
最初のループが、[(0, 0), (1, 0),...,(4,0)]となるようだ。

この多重ループによる処理は以下のサイトを参考にさせていただきました。
【Python】リスト内包表記 x 多重ループ - コンパイラかく語りき

終わりに

Pythonの内包表記は最初は理解しにくいし、とっつきにくい。
しかし、上記で見てきたように単なるfor文の書き方を変えたに過ぎず、
慣れるとコードも短くなり、読みやすくなる。
Pythonの本を読んでいると絶対出てくる表現でもあるので、
理解さえしておけば、後は書いていくうちに慣れてくるはず。

*1:入門Python3 P104