Pythonのリスト内包表記で色々な数列を作ってみた
Pythonのリスト内包表記の細かい動作がようやく分かってきたので、ブログに書いておく。
基本
Google先生に聞いたら詳しい説明が出てくるので、簡単に。
リスト内包表記は、下記のように書くとリストが作れる記法です。
>>> [x for x in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
これだけだと微妙ですが、例えば
>>> [x*x for x in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
とべき乗のリストを作ってみたり、
>>> [x for x in range(10) if x % 2 == 0] [0, 2, 4, 6, 8]
とif文を使い、偶数だけのリストを作ったりもできます。
処理の順番は下記の順番のようです。*1
>>> [ (4) for (2) in (1) if (3) ]
(1), (2)・・・リストの値を一つずつ(2)の変数に入れる
(3) ・・・条件式がTrueなら(4)の処理を行う
(4) ・・・処理
実践的なリスト内包表記
さて、リスト内包表記が使えるようになったけど、実際に使うときはもう少し複雑なリストが欲しくなりますよね。
という訳で、より実践的なリストの例を考えてみました。
- もちろん、リスト内包表記のみで作成する
- >>> [... for ... in ... if ...]の形のみです
- 事前にimportしておいたり、変数を用意するのは禁止
- 九九表(二次元リスト)
- [ [1, 2, 3, ... ,9], [2, 4, ... , 18], ... , [9, 18, ... , 81] ]
- 素数
- [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
- フィボナッチ数列
- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
注意)この下に答えを書いてしまうので、上記の数列を自分で作成してみたい人は「続きを読む」をクリックしないように
直接、このページに来た人用に、無駄に改行を入れてあります。
九九表
これはリスト内包表記を2つ作らないといけないです。
- 九九表のリスト内包表記の例
>>> [[x*y for x in range(1,10)] for y in range(1, 10)] [[1, 2, 3, 4, 5, 6, 7, 8, 9], [2, 4, 6, 8, 10, 12, 14, 16, 18], [3, 6, 9, 12, 15, 18, 21, 24, 27], [4, 8, 12, 16, 20, 24, 28, 32, 36], [5, 10, 15, 20, 25, 30, 35, 40, 45], [6, 12, 18, 24, 30, 36, 42, 48, 54], [7, 14, 21, 28, 35, 42, 49, 56, 63], [8, 16, 24, 32, 40, 48, 56, 64, 72], [9, 18, 27, 36, 45, 54, 63, 72, 81]]
これは↓のように書き換えると、なんとなく分かるかも。
[ ( ) for y in range(1, 10) ]
評価の順番的に y が作られた後に ( ) の部分が処理される訳ですね。
このため、( )内で y を使ってもエラーになりません。
素数
普段はエラトステネスの篩(ふるい) - Wikipediaを使うと思いますが、リスト内包表記の中で再帰呼び出しは使えないため、ここは素直に「1とその数自身以外に正の約数がない(割り切れない)」という条件を使います。
- 素数のリスト内包表記の例
>>> [x for x in range(2, 100) if len([y for y in range(2, x) if x % y == 0]) == 0] [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
今回はifのlen()の中だけに注目すると分かりやすくなる。
[y for y in range(2, x) if x % y == 0]
これは“xの1とx以外の約数”のリストを作っている。
このリストの“要素数(割り切れた数)が0”であれば素数である。つまり
if len([y for y in range(2, x) if x % y == 0]) == 0
がifの条件となる。
if文が分かれば、後は簡単ですよね。
フィボナッチ数列
フィボナッチ数列とは、「どの項もその前の2つの項の和となる数列」です。
[0, 1, 1, 2, 3, 5, 8, 13, ... ] 0 1 1 = 0 + 1 2 = 1 + 1 3 = 1 + 2 5 = 3 + 2 ...
詳しくはフィボナッチ数 - Wikipediaを読んでください。
フィボナッチ数列を作るためには、再帰 or 変数が必要なのですが、
いくら考えても解法が思い浮かばず、「フィボナッチ数列は無理なのだろう」と考えていました。
と思ってたら、@tosikawaさんからリプライが。
>>> [(seq.append(seq[i-2] + seq[i-1]), seq[i-2])[1] for seq in [[0, 1]] for i in range(2, 20)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
_, ._
(;゚ Д゚) …フィボナッチがデキテル?!
という訳で、頑張って解読してみた。
まず、18回ループするようにカウンタを作っているっぽい。
for i in range(2, 20)
次にフィボナッチを入れるリストを作る。
for seq in [[0, 1]]
そして、“その前の2つの項の和”をappendでリストに追加している。
(seq.append(seq[i-2] + seq[i-1]), seq[i-2])[1]
ただ、リストのappendの戻り値がNoneのため、このままだと[None, None, ...]のリストになってしまう。
そこで、タプルにして、( (処理), (戻り値) )[1]と言う方法で値を返している。
これは、少しソースを弄って、seqの値を見ると分かるかも。*2
>>> [(seq.append(seq[i-2] + seq[i-1]), seq)[1] for seq in [[0, 1]] for i in range(2, 5)] [[0, 1, 1, 2, 3], [0, 1, 1, 2, 3], [0, 1, 1, 2, 3]]
ここまでリスト内包表記を書いてみて
考えて、解法が見つかった時は凄く面白いし、lambda、組み込み関数、シーケンス型の勉強にもなりました。
ただ、やっぱり普通にfor文や関数の再帰呼び出しとかでリスト作った方がソースの可読性は良いのだろうなぁ。
リスト内包表記の使用は容量用法を守り、適度に使用するのが良さそうです。