TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

Pythonのタプルのリストから列を抽出する

Pythonで、タプルのリストから列を抽出する3種類の方法を比較してみた。

次のような要素が2つのタプルのリストからそれぞれの列を抽出する場合を考える。

抽出前

[('a', 1), ('b', 2), ('c', 3), ('d', 4)]

抽出後

(['a', 'b', 'c', 'd'], [1, 2, 3, 4])

方法1:for文を使った素朴な方法

def func1(samples):
    list1 = []
    list2 = []
    for a, b in samples:
        list1.append(a)
        list2.append(b)
    return list1, list2

Pythonのfor文は遅く、この方法は一般的に性能が良くない。

方法2:リスト内包表記を使った方法

def func2(samples):
    return [a for a, b in samples], [b for a, b in samples]

リスト内包表記を2回使って、それぞれの列を抽出している。
リスト内包表記のforを2回実行するが、方法1よりはましな結果になる。

方法3:zipとリスト内包表記を使った方法

def func3(samples):
    return [list(tup) for tup in zip(*original)]

一見、何をしているかわからないが、*でリストを展開して複数のタプルにして、zipで1番目の列のタプル、2番目の列のタプル、・・・として取り出している。
こうするとわかりやすい。

[tup for tup in zip(('a',1),('b',2),('c',3),('d',4))]

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]

この方法は、下記の記事で紹介されていた。
python - Transpose/Unzip Function (inverse of zip)? - Stack Overflow

性能測定

それぞれの方法の性能を比較してみた。
上記の例では要素数が少ないので、要素数をアルファベット26文字分に増やして測定する。

original = [(chr(i), i) for i in range(97, 97+26)]

import timeit
timeit.timeit(lambda: func1(original))
timeit.timeit(lambda: func2(original))
timeit.timeit(lambda: func3(original))

2.5222641000000294
1.7053095999999641
1.5412319000001844

方法3が一番速いことが確認できた。