リストをn個ずつのサブリストに分割 (Python)
愚直にやればもちろんできるんだけど、パフォーマンスが気になったり、かっこよく書きたいとか思うわけです。
>>> a = range(15) >>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] >>> [a[i:i+3] for i in range(0,15,3)] [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]]
range(0, 15, 3) が [0, 3, 6, 9, 12] に展開されて、スライスしていく感じ。
zip(*[iter(s)]*n) で n 個ずつ分割
探しているとこんなのがでてくる。
iterable の、左から右への評価順序が保証されます。そのため zip(*[iter(s)]*n) を使ってデータ系列を n 長のグループにするクラスタリングすることができます。
ということで、15個の要素を持つリストを3個ずつのサブリストに分割したい場合、
>>> zip(*[iter(range(15))]*3) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]
となるのだそうだ。なんで?
zip関数
そもそもzip関数は何をするかというと、たとえば、以下の例。
>>> zip(['a','b','c'], ['A','B','C'], ['1','2','3']) [('a', 'A', '1'), ('b', 'B', '2'), ('c', 'C', '3')]
引数に渡したイテレータを順番になめて、要素をとってきて、tupleにする。 keyとvalueのリストから辞書を作るときにつかうやつです。
>>> key = ['a', 'b', 'c'] >>> val = ['A', 'B', 'C'] >>> dict(zip(key,val)) {'a': 'A', 'c': 'C', 'b': 'B'}
引数が同じイテレータだったら
ということを踏まえて、先ほどのコード。同じイテレータが引数に渡されている。
>>> zip(*[iter(range(15))]*3) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]
展開するとこう。
>> x = iter(range(15)) >>> zip(x, x, x) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]
なるほど!
このあたりに解説があった。
残念な点
最高にクールでかっこいいのだが、余りがある場合は捨てられる。
>>> zip(*[iter(range(16))]*3) [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]
itertools.izip_longest
ちゃんと余った要素もリストにしてほしい。ということで、そのときは zip の代わりに itertools.izip_longest を使えばいいようだ。
>>> for a in itertools.izip_longest(*[iter(range(16))]*3): ... print a ... (0, 1, 2) (3, 4, 5) (6, 7, 8) (9, 10, 11) (12, 13, 14) (15, None, None)
ふむ。
2013/07/11 追記
いろいろブコメありがとうございました!
id:kk6 more_itertools.chunked も同じような実装
恥ずかしながらmore_itertoolsを知りませんでした。itertoolsも忘却の彼方でしたし。。
欲しかったのはズバリこれでした。
id:tomoemon パッと見てイミフなコードは3ヶ月後に見たらまたイミフになるだけ。そういう "beautiful code" が本当に必要な場面かどうかよく吟味して使いたい
はい、そのとおりです。 トリッキーなコードをむやみに書くべきではないのですが、パフォーマンス的に、直感的でなかったり、意図を汲み取りにくいコードが必要になる場合はあると思います。(スクリプト言語は静的言語ほどの最適化が走らないので余計に)
とはいえ、こんなコード片をそのままリリースしてたら、レビューで意味わからんと怒られてしまいます。ということで、
- これは何をしてるんだということをコメントで書いておく
- コードレビューではより細かく補足することも
- 場合によっては、メソッドを分離してテストを書く
- メソッドとして独立させることで、メソッド名と引数、ドキュメントで意味がわかりやすくなる
- テストを書くことでさらに挙動を追いやすくなる。というかこの手のトリッキーなコードは特異条件だけで成立するようなことも多く (上でいうと、割り切れないと切り落とされるとか)、そこを補完するのにも役立つと思います。
というのは意識しています。