TL;DR
zip()は複数のイテラブルを受け取り、タプルで返す。zip(*[iter(s)]*n)
を使うことでイテラブルを分割することができる。
zip() のポイント
- 複数のイテラブルからイテレータを作る
- イテラブルの中で最短のものが尽きると停止する
zip(*[iter(s)]*n)
でイテラブルを分割することができる- 余りを表示するには zip_longest()関数をインポートする
a = zip('ABCD', '12')
list(a)
# [('A', '1'), ('B', '2')]
複数のリスト
複数のリストを適用すると、最も短いイテラブルの数で停止する。
x = [1,2,3]
y = [4,5,6,7]
z = [8,9] # → 一番短いイテラブル
list(zip(x,y,z))
# [(1, 4, 8), (2, 5, 9)]
for 文 で使う
もちろん、for 文で使うこともできる。
a = [1, 2, 3, 4]
b = ['A', 'B']
c = ['い', 'ろ', 'は']
for a, b, c in zip(a, b, c):
print(a, b, c)
# 1 A い
# 2 B ろ
イテラブルを分割する
zip(*[iter(s)]*n)
を使えば、イテラブルを n 回分割することができる。
b = [1, 2, 3, 4, 5, 6, 7]
c = zip(*[iter(b)]*2)
list(c)
# [(1, 2), (3, 4), (5, 6)]
中身を見てみると、同じイテレータを参照している。
b = [1, 2, 3, 4, 5, 6, 7]
[iter(b)]*2
# [<list_iterator at 0x7f7eab2ad790>,
# <list_iterator at 0x7f7eab2ad790>]
公式サイトには以下のように書かれている。
zip(*[iter(s)]*n)
を使ってデータ系列を長さ n のグループにクラスタリング(グループ分け)するイディオム(慣用表現)が使えます。これは、各出力タプルがイテレータを n 回呼び出した結果となるよう、 同じ イテレータを n 回繰り返します。これは入力を長さ n のチャンクに分割する効果があります。 組み込み関数 — Python 3.9.4 ドキュメント
しかし、なぜこうなるのか?
iterator-zip(_ [iter(s)] _ n)は Python でどのように機能しますか? - スタックオーバーフローに解説があった。例えば以下の場合、
s = [1,2,3,4,5,6,7,8,9]
n = 3
list(zip(*[iter(s)]*n))
# [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
上を展開すると以下になる。同じイテレータを 3 回渡すと、zip()されるたびにイテレータからアイテムがプルされる。なお、余りは捨てられる。
s = iter([1,2,3,4,5,6,7,8,9])
list(zip(s, s, s))
# [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
zip_longest():一番長いイテラブルを出力
余りを表示するには、intertools.zip_longest()関数をインポートするとよい。一番長いイテラブルに合わせることができる。
from itertools import zip_longest
b = [1, 2, 3, 4, 5, 6, 7]
c = zip_longest(*[iter(b)]*2)
list(c)
# [(1, 2), (3, 4), (5, 6), (7, None)]
fillvalue:任意の値で埋める
None 以外の値で埋めたいときは、どうすればいいのか?引数に fillvalue を指定することで、任意の値で埋めることができる。
from itertools import zip_longest
x = [1,2,3]
y = [4,5,6,7]
z = [8,9]
list(zip_longest(x, y, z, fillvalue=100))
# [(1, 4, 8), (2, 5, 9), (3, 6, 100), (100, 7, 100)]