home changes contents help options

087:複数の配列を並行処理する

(Ruby の配列に相当する Python の型はリストです。)

zip 関数

組み込み関数 の zip でシーケンスの各要素を並べたリストを作ることができます。

>>> a = ['a', 'b', 'c']
>>> b = [0, 1, 2]
>>> c = ['A', 'B', 'C']
>>>
>>> zip(a, b)
[('a', 0), ('b', 1), ('c', 2)]
>>> zip(a, b, c)
[('a', 0, 'A'), ('b', 1, 'B'), ('c', 2, 'C')]

itertools の izip 関数で、シーケンスの各要素をまとめて一つずつ得るイテレータが得られます。

>>> from itertools import izip
>>> izip(a, b)
<itertools.izip object at 0x00AFBA80>
>>> list(_)
[('a', 0), ('b', 1), ('c', 2)]

こうして得られたリスト、イテレータを for 文でまわすことにより複数の配列を並行処理することができます。

>>> indexes = [1, 2, 3, 4, 5]
>>> titles = [
...   u'やる気を高めよう',
...   u'Python インタプリタを使う',
...   u'形式ばらない Python の紹介',
...   u'その他の制御フローツール',
...   u'データ構造']
>>>
>>> for title, index in izip(indexes, titles):
...   print u'%d: %s' % (title, index)
...
1: やる気を高めよう
2: Python インタプリタを使う
3: 形式ばらない Python の紹介
4: その他の制御フローツール
5: データ構造

雑多な話

zip, itertools.izip の引数の型

本レシピはリストに関する内容なので上記の例にて引数にリストを用いていますが、 zip の引数はリストに限りません。イテレート可能オブジェクトであれば受け付けます。

>>> zip((1, 2, 3), set(['a', 'b', 'c']))
[(1, 'a'), (2, 'c'), (3, 'b')]

zip, itertools.izip の引数の長さが不ぞろいの場合

各引数の長さが不ぞろいである場合は、一番短いものに合うようになっています。

>>> zip([1, 2, 3], [4, 5], [6])
[(1, 4, 6)]
>>>
>>> zip([1, 2, 3], open(u'087_複数の配列を並行処理する.txt'))
[(1, '087:\x95\xa1\x90\x94\x82\xcc\x94z\x97\xf1\x82\xf0\x95\xc0\x8ds\x8f\x88\x97
\x9d\x82\xb7\x82\xe9 \n'), (2, '========================================\n'), (3
, '\n')]

短いものの取出しが終わってしまっても、その後ろに何か、たとえば None が並んでいるとみなせると便利なことがあります。この動作は Python 2.6 で追加された itertools.izip_longest を用いると実現できます。

>>> from itertools import izip_longest
>>> izip_longest([1, 2, 3], [4, 5], [6])
<itertools.izip_longest object at 0x00A4ADB0>
>>> list(_)
[(1, 4, 6), (2, 5, None), (3, None, None)]
>>>
>>> izip_longest([1, 2, 3], [4, 5], [6], fillvalue=0)
<itertools.izip_longest object at 0x00A4ADB0>
>>> list(_)
[(1, 4, 6), (2, 5, 0), (3, 0, 0)]

Python 2.3, 2.4, 2,5 には izip_longest はありません。けれども itertools の izip, repeat, chain を用いて作ることはできます。

from itertools import izip, repeat, chain

def izip_longest(*args, **kwds):
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
    fillvalue = kwds.get('fillvalue')
    def sentinel(counter = ([fillvalue]*(len(args)-1)).pop):
        yield counter()         # yields the fillvalue, or raises IndexError
    fillers = repeat(fillvalue)
    iters = [chain(it, sentinel(), fillers) for it in args]
    try:
        for tup in izip(*iters):
            yield tup
    except IndexError:
        pass

Python 2 では map 関数の第 1 引数に None を与えると zip 関数に似た挙動をすることをを利用して、次のようなコードを書くことができます。要素の不足分は None で補われます。しかし、 Python 3 では map 関数の動作が変わっているため使えない手法です。

>>> map(None, [1, 2, 3], [4, 5], [6])
[(1, 4, 6), (2, 5, None), (3, None, None)]

Python 3 の変更点

Python 3 の zip 関数はイテレータを返します。つまり Python 2 の itertools.izip と同じです。リストがいりようであれば list 関数にイテレータを渡してください。

>>> zip([1, 2, 3], [4, 5, 6])
<zip object at 0x00BA98A0>
>>> list(_)
[(1, 4), (2, 5), (3, 6)]

Python 2.6 で Python 3 版 zip

Python 2 、 3 間で機能が変わっている関数がいくつか存在します。 zip もその一つです。この差異を吸収するために Python 2.6 には future_builtins モジュールが追加されています。

>>> from future_builtins import *
>>> zip([1, 2, 3], [4, 5, 6])
<itertools.izip object at 0x00A661E8>
>>> list(_)
[(1, 4), (2, 5), (3, 6)]

ちなみに Python 2.6 の future_builtins の実装は zip = itertools.izip という簡単なものです。