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])
それを行う組み込み関数はありますか?
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])
それを行う組み込み関数はありますか?
回答:
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
直接(タプルに変換された後)に渡されるため、引数の数が大きくなりすぎることを心配する必要はありません。
zip([], [])
この方法で解凍しても取得できません[], []
。それはあなたを取得します[]
。のみ...
zip
リストの代わりにイテレータを返すことを除いて、Python 3とまったく同じように動作します。上記と同じ出力を取得するには、zip呼び出しをリストにラップする必要があります。出力され list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))
ます[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
list
sで結構です。しかし、(list
の結果を確認することによってzip
)一度に完全な結果を実現しようとすると、大量のメモリを使用する可能性があります(すべてのtuple
を一度に作成する必要があるため)。明示zip
せずに結果を繰り返し処理できればlist
、メモリを大幅に節約できます。他の唯一の問題は、入力に多くの要素があるかどうかです。コストとしては、すべてを引数としてアンパックzip
する必要があり、すべてのイテレータを作成して格納する必要があります。これは、s が非常に長い場合list
の実際の問題にすぎません(数十万以上の要素と考えてください)。
あなたもできる
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 ))
ジェネレータは、各要素を要求するまでリストを参照しませんが、一方で、元のリストへの参照を維持します。
zip(*x)
バージョンよりも「拡張性」が高くありません。zip(*x)
ループを1回通過するだけで済み、スタック要素を使い果たしません。
zip
、転置されたデータが使用されてすぐに破棄され、元のリストがメモリにずっと長く留まるというユースケースである場合にのみ使用するよりも優れています。
同じ長さではないリストがある場合、パトリックスの回答に従って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()の方がわずかに高速です。
izip_longest
zip_longest
python3ユーザーとして知られています。
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 == j
、n == 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
、元の構造に戻るために再び使用することはできません。iterable
result
tuple
>>> transpose_finite_iterable(result)
... hangs ...
Traceback (most recent call last):
File "...", line 1, in ...
File "...", line 2, in transpose_finite_iterable
MemoryError
それでは、このケースにどのように対処できますか?
deque
itertools.tee
functionのドキュメントを確認した後、いくつかの変更を加えた場合に役立つ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項演算子のクラスで、独自の逆関数(数学者がこの種の関数を「インボリューション」と呼ぶ)と見なすことができます。
singledispatch
ingのボーナスとして、次の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
、誰かが持っているしたい場合は返すイテレータとtuple
のlist
OPのように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)]
潜在的に無限のイテラブルまたは潜在的に無限のイテラブルを処理するための解決策はありません(少なくとも明白です)が、このケースはあまり一般的ではありません。
more_itertools.unzipの使用を検討してください。
>>> from more_itertools import unzip
>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> [list(x) for x in unzip(original)]
[['a', 'b', 'c', 'd'], [1, 2, 3, 4]]
タプルを返す(そして大量のメモリを使用する可能性がある)ので、この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
上記の回答のいずれも、必要な出力を効率的に提供しません。これは、タプルのリストではなく、リストのタプルです。前者は、使用することができますtuple
しmap
。ここに違いがあります:
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の場合、イテレータなどの関数に結果を渡すlist
かtuple
、イテレータを使い果たす必要があります。メモリ効率の高いイテレータの場合は、外側list
を省略しtuple
て、それぞれのソリューションを呼び出すことができます。
一方で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コレクションを使用することが理にかなっている可能性があります。
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