転置/解凍機能(zipの逆)?


505

2項目のタプルのリストがあり、それらを2つのリストに変換したいと思います。最初のリストには各タプルの最初の項目が含まれ、2番目のリストには2番目の項目が含まれています。

例えば:

original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
# and I want to become...
result = (['a', 'b', 'c', 'd'], [1, 2, 3, 4])

それを行う組み込み関数はありますか?


6
以下のすばらしい答え、
そして

3
リストの代わりにジェネレータで同じことを行うには、この素晴らしい答えを参照してください:how-to-unzip-an-iterator
YvesgereY

回答:


778

zip独自の逆です!特別な*演算子を使用する場合。

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

これが機能する方法はzip、引数を指定して呼び出すことです。

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

…引数がzip直接(タプルに変換された後)に渡されるため、引数の数が大きくなりすぎることを心配する必要はありません。


20
ああ、それがとても簡単だったら。zip([], [])この方法で解凍しても取得できません[], []。それはあなたを取得します[]。のみ...
user2357112はモニカ

4
これはPython3では機能しません。参照:stackoverflow.com/questions/24590614/...
トミー

31
@Tommy不正解です。zipリストの代わりにイテレータを返すことを除いて、Python 3とまったく同じように動作します。上記と同じ出力を取得するには、zip呼び出しをリストにラップする必要があります。出力され list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))ます[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
MJeffryes

4
注意:非常に長いリストでは、メモリとパフォーマンスの問題に対処できます。
Laurent LAPORTE

1
@JohnP:listsで結構です。しかし、(listの結果を確認することによってzip)一度に完全な結果を実現しようとすると、大量のメモリを使用する可能性があります(すべてtupleを一度に作成する必要があるため)。明示zipせずに結果を繰り返し処理できればlist、メモリを大幅に節約できます。他の唯一の問題は、入力に多くの要素があるかどうかです。コストとしては、すべてを引数としてアンパックzipする必要があり、すべてのイテレータを作成して格納する必要があります。これは、s が非常に長い場合listの実際の問題にすぎません(数十万以上の要素と考えてください)。
ShadowRanger

29

あなたもできる

result = ([ a for a,b in original ], [ b for a,b in original ])

それはする必要があり、より良いスケール。特に、Pythonが必要でない限りリスト内包表記を拡張しないことでうまくいく場合。

(ちなみに、これは、タプルのリストではなく、2タプル(ペア)のリストを作成zipします。)

実際のリストの代わりにジェネレータが問題なければ、これはそれを行います:

result = (( a for a,b in original ), ( b for a,b in original ))

ジェネレータは、各要素を要求するまでリストを参照しませんが、一方で、元のリストへの参照を維持します。


8
「特にPythonが必要でない限り、リスト内包表記を拡張しないことを好む場合。」mmm ...通常、リスト内包表記はすぐに展開されます-または何か問題がありますか?
glglgl 2011

1
@glglgl:いいえ、あなたはおそらく正しいです。将来のバージョンが正しく動作するようになることを期待していました。(変更することは不可能ではありません。変更が必要な副作用のセマンティクスはおそらくすでに推奨されていません。)
Anders Eurenius

9
あなたが手に入れたいのは、すでに存在するジェネレータ式です。
glglgl 2012年

12
これは、zip(*x)バージョンよりも「拡張性」が高くありません。zip(*x)ループを1回通過するだけで済み、スタック要素を使い果たしません。
habnabit 2013年

1
「より適切にスケーリング」されるかどうかは、転置されたデータと比較した元のデータのライフサイクルに依存します。この答えはzip、転置されたデータが使用されてすぐに破棄され、元のリストがメモリにずっと長く留まるというユースケースである場合にのみ使用するよりも優れています。
Ekevoo 2015年

21

同じ長さではないリストがある場合、パトリックスの回答に従ってzipを使用したくない場合があります。これは機能します:

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

ただし、長さのリストが異なる場合、zipは各項目を最短リストの長さに切り詰めます。

>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e')]

関数なしでmapを使用して、空の結果をNoneで埋めることができます。

>>> map(None, *[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e'), (1, 2, 3, 4, None)]

ただし、zip()の方がわずかに高速です。


4
次も使用できますizip_longest
Marcin

3
zip_longestpython3ユーザーとして知られています。
zezollo 2016年

1
@GrijeshChauhan私はこれが本当に古いことを知っていますが、それは奇妙な組み込み機能です:docs.python.org/2/library/functions.html#map「関数がNoneの場合、恒等関数と見なされます;複数の引数がある場合、 map()は、すべての反復可能オブジェクトからの対応する項目を含むタプルで構成されるリストを返します(一種の転置演算)。反復可能引数は、シーケンスまたは任意の反復可能オブジェクトであり、結果は常にリストです。 "
cactus1 2017

18

私は次のようzip(*iterable)に私のプログラムで(あなたが探しているコードの一部です)を使用するのが好きです:

def unzip(iterable):
    return zip(*iterable)

私はunzipもっと読みやすいと思います。


12
>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> tuple([list(tup) for tup in zip(*original)])
(['a', 'b', 'c', 'd'], [1, 2, 3, 4])

質問のようにリストのタプルを与えます。

list1, list2 = [list(tup) for tup in zip(*original)]

2つのリストを解凍します。


8

素朴なアプローチ

def transpose_finite_iterable(iterable):
    return zip(*iterable)  # `itertools.izip` for Python 2 users

以下のように示すことができる(潜在的に無限の)反復可能の有限反復可能(たとえば、list/ tuple/のようなシーケンスstr)でうまく機能します

| |a_00| |a_10| ... |a_n0| |
| |a_01| |a_11| ... |a_n1| |
| |... | |... | ... |... | |
| |a_0i| |a_1i| ... |a_ni| |
| |... | |... | ... |... | |

どこ

  • n in ℕ
  • a_ij-thイテラブルのj-th要素に対応しi

申請後transpose_finite_iterable

| |a_00| |a_01| ... |a_0i| ... |
| |a_10| |a_11| ... |a_1i| ... |
| |... | |... | ... |... | ... |
| |a_n0| |a_n1| ... |a_ni| ... |

このようなケースのPythonの例ここでa_ij == jn == 2

>>> from itertools import count
>>> iterable = [count(), count()]
>>> result = transpose_finite_iterable(iterable)
>>> next(result)
(0, 0)
>>> next(result)
(1, 1)

ただし、無限反復可能(この場合はs)の無限反復可能であるためtranspose_finite_iterable、元の構造に戻るために再び使用することはできません。iterableresulttuple

>>> transpose_finite_iterable(result)
... hangs ...
Traceback (most recent call last):
  File "...", line 1, in ...
  File "...", line 2, in transpose_finite_iterable
MemoryError

それでは、このケースにどのように対処できますか?

...そしてここに来る deque

itertools.teefunctionのドキュメントを確認した後、いくつかの変更を加えた場合に役立つPythonレシピがあります。

def transpose_finite_iterables(iterable):
    iterator = iter(iterable)
    try:
        first_elements = next(iterator)
    except StopIteration:
        return ()
    queues = [deque([element])
              for element in first_elements]

    def coordinate(queue):
        while True:
            if not queue:
                try:
                    elements = next(iterator)
                except StopIteration:
                    return
                for sub_queue, element in zip(queues, elements):
                    sub_queue.append(element)
            yield queue.popleft()

    return tuple(map(coordinate, queues))

確認しよう

>>> from itertools import count
>>> iterable = [count(), count()]
>>> result = transpose_finite_iterables(transpose_finite_iterable(iterable))
>>> result
(<generator object transpose_finite_iterables.<locals>.coordinate at ...>, <generator object transpose_finite_iterables.<locals>.coordinate at ...>)
>>> next(result[0])
0
>>> next(result[0])
1

合成

これで、反復可能の反復可能オブジェクトを操作するための一般的な関数を定義できます。その1つは有限で、もう1つは次のようなfunctools.singledispatchデコレータを使用して潜在的に無限です

from collections import (abc,
                         deque)
from functools import singledispatch


@singledispatch
def transpose(object_):
    """
    Transposes given object.
    """
    raise TypeError('Unsupported object type: {type}.'
                    .format(type=type))


@transpose.register(abc.Iterable)
def transpose_finite_iterables(object_):
    """
    Transposes given iterable of finite iterables.
    """
    iterator = iter(object_)
    try:
        first_elements = next(iterator)
    except StopIteration:
        return ()
    queues = [deque([element])
              for element in first_elements]

    def coordinate(queue):
        while True:
            if not queue:
                try:
                    elements = next(iterator)
                except StopIteration:
                    return
                for sub_queue, element in zip(queues, elements):
                    sub_queue.append(element)
            yield queue.popleft()

    return tuple(map(coordinate, queues))


def transpose_finite_iterable(object_):
    """
    Transposes given finite iterable of iterables.
    """
    yield from zip(*object_)

try:
    transpose.register(abc.Collection, transpose_finite_iterable)
except AttributeError:
    # Python3.5-
    transpose.register(abc.Mapping, transpose_finite_iterable)
    transpose.register(abc.Sequence, transpose_finite_iterable)
    transpose.register(abc.Set, transpose_finite_iterable)

これは、有限の空でない反復可能オブジェクトに対する2項演算子のクラスで、独自の逆関数(数学者がこの種の関数をインボリューション」と呼ぶ)と見なすことができます。


singledispatchingのボーナスとして、次のnumpyような配列を処理できます。

import numpy as np
...
transpose.register(np.ndarray, np.transpose)

そしてそれを次のように使用します

>>> array = np.arange(4).reshape((2,2))
>>> array
array([[0, 1],
       [2, 3]])
>>> transpose(array)
array([[0, 2],
       [1, 3]])

注意

以来transpose、誰かが持っているしたい場合は返すイテレータとtuplelistOPのようにSを-これはでさらに行うことができmap、内蔵機能のような

>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> tuple(map(list, transpose(original)))
(['a', 'b', 'c', 'd'], [1, 2, 3, 4])

広告

私はへの一般化ソリューション追加したlzパッケージからの0.5.0ように使用することができますバージョンを

>>> from lz.transposition import transpose
>>> list(map(tuple, transpose(zip(range(10), range(10, 20)))))
[(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), (10, 11, 12, 13, 14, 15, 16, 17, 18, 19)]

PS

潜在的に無限のイテラブルまたは潜在的に無限のイテラブルを処理するための解決策はありません(少なくとも明白です)が、このケースはあまり一般的ではありません。


4

これは別の方法にすぎませんが、非常に役立ちましたので、ここに書きます。

このデータ構造を持つ:

X=[1,2,3,4]
Y=['a','b','c','d']
XY=zip(X,Y)

その結果:

In: XY
Out: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

それを解凍して元に戻すよりパイソン的な方法は私の意見ではこれです:

x,y=zip(*XY)

しかし、これはタプルを返すので、リストが必要な場合は使用できます:

x,y=(list(x),list(y))


1

タプルを返す(そして大量のメモリを使用する可能性がある)ので、このzip(*zipped)トリックは私にとっては、有用というより賢いようです。

これは実際にzipの逆を与える関数です。

def unzip(zipped):
    """Inverse of built-in zip function.
    Args:
        zipped: a list of tuples

    Returns:
        a tuple of lists

    Example:
        a = [1, 2, 3]
        b = [4, 5, 6]
        zipped = list(zip(a, b))

        assert zipped == [(1, 4), (2, 5), (3, 6)]

        unzipped = unzip(zipped)

        assert unzipped == ([1, 2, 3], [4, 5, 6])

    """

    unzipped = ()
    if len(zipped) == 0:
        return unzipped

    dim = len(zipped[0])

    for i in range(dim):
        unzipped = unzipped + ([tup[i] for tup in zipped], )

    return unzipped

タプルを継続的に再作成することは、私にはそれほど効率的ではないように思われますが、メモリを事前に割り当てる可能性がある両端キューを使用してこのアプローチを拡張できます。
チャーリークラーク

0

上記の回答のいずれも、必要な出力を効率的に提供しません。これは、タプルのリストではなく、リストのタプルです。前者は、使用することができますtuplemap。ここに違いがあります:

res1 = list(zip(*original))              # [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
res2 = tuple(map(list, zip(*original)))  # (['a', 'b', 'c', 'd'], [1, 2, 3, 4])

さらに、以前のソリューションのほとんどはPython 2.7を想定しておりzip、イテレータではなくリストを返します。

Python 3.xの場合、イテレータなどの関数に結果を渡すlisttuple、イテレータを使い果たす必要があります。メモリ効率の高いイテレータの場合は、外側listを省略しtupleて、それぞれのソリューションを呼び出すことができます。


0

一方でzip(*seq)非常に便利です、。それはに渡される値のタプルを作成するように非常に長いシーケンスには不向きかもしれ例えば、私は万人以上のエントリを持つ座標系に取り組んできましたし、それがsignifcantly速く作成するために見つけますシーケンスを直接。

一般的なアプローチは次のようなものです。

from collections import deque
seq = ((a1, b1, …), (a2, b2, …), …)
width = len(seq[0])
output = [deque(len(seq))] * width # preallocate memory
for element in seq:
    for s, item in zip(output, element):
        s.append(item)

ただし、結果をどのように処理するかによって、コレクションの選択は大きな違いを生む可能性があります。私の実際の使用例では、内部ループなしでセットを使用すると、他のすべてのアプローチよりも著しく高速です。

また、他の人が指摘しているように、データセットでこれを行う場合は、代わりにNumpyまたはPandasコレクションを使用することが理にかなっている可能性があります。


0

numpy配列とパンダの方が好ましい場合がありますが、この関数はとしてzip(*args)呼び出されunzip(args)たときの動作を模倣します。

args値を反復処理するときにジェネレーターを渡すことができます。コンテナの初期化を装飾clsまたはmain_clsマイクロ管理します。

def unzip(items, cls=list, main_cls=tuple):
    """Zip function in reverse.

    :param items: Zipped-like iterable.
    :type  items: iterable

    :param cls: Callable that returns iterable with callable append attribute.
        Defaults to `list`.
    :type  cls: callable, optional

    :param main_cls: Callable that returns iterable with callable append
        attribute. Defaults to `tuple`.
    :type  main_cls: callable, optional

    :returns: Unzipped items in instances returned from `cls`, in an instance
        returned from `main_cls`.

    :Example:

        assert unzip(zip(["a","b","c"],[1,2,3])) == (["a","b",c"],[1,2,3])
        assert unzip([("a",1),("b",2),("c",3)]) == (["a","b","c"],[1,2,3])
        assert unzip([("a",1)], deque, list) == [deque(["a"]),deque([1])]
        assert unzip((["a"],["b"]), lambda i: deque(i,1)) == (deque(["b"]),)
    """
    items = iter(items)

    try:
        i = next(items)
    except StopIteration:
        return main_cls()

    unzipped = main_cls(cls([v]) for v in i)

    for i in items:
        for c,v in zip(unzipped,i):
            c.append(v)

    return unzipped
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.