Transpose/Unzip-Funktion (Umkehrung von zip)?
Frage
Ich habe eine Liste von Tupeln mit zwei Elementen und möchte sie in zwei Listen konvertieren, wobei die erste Liste das erste Element in jedem Tupel und die zweite Liste das zweite Element enthält.
Zum Beispiel:
original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
# and I want to become...
result = (['a', 'b', 'c', 'd'], [1, 2, 3, 4])
Gibt es eine eingebaute Funktion, die das erledigt?
Lösung
zip
ist seine eigene Umkehrung!Vorausgesetzt, Sie verwenden den speziellen *-Operator.
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)])
[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Dies funktioniert per Anruf zip
mit den Argumenten:
zip(('a', 1), ('b', 2), ('c', 3), ('d', 4))
… außer dass die Argumente an übergeben werden zip
direkt (nach der Konvertierung in ein Tupel), sodass Sie sich keine Sorgen machen müssen, dass die Anzahl der Argumente zu groß wird.
Andere Tipps
Das könntest du auch tun
result = ([ a for a,b in original ], [ b for a,b in original ])
Es sollen besser skalieren.Vor allem, wenn Python darauf achtet, die Listenverständnisse nicht zu erweitern, es sei denn, dies ist erforderlich.
(Übrigens erstellt es ein 2-Tupel (Paar) von Listen und nicht wie eine Liste von Tupeln zip
tut.)
Wenn Generatoren anstelle tatsächlicher Listen in Ordnung sind, würde dies Folgendes bewirken:
result = (( a for a,b in original ), ( b for a,b in original ))
Die Generatoren durchforsten die Liste erst, wenn Sie nach den einzelnen Elementen fragen, behalten aber andererseits Verweise auf die Originalliste bei.
Wenn Sie Listen haben, die nicht die gleiche Länge haben, möchten Sie gemäß Patricks Antwort möglicherweise nicht zip verwenden.Das funktioniert:
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)])
[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Aber bei Listen unterschiedlicher Länge kürzt zip jedes Element auf die Länge der kürzesten Liste:
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e')]
Sie können eine Karte ohne Funktion verwenden, um leere Ergebnisse mit „Keine“ zu füllen:
>>> map(None, *[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e'), (1, 2, 3, 4, None)]
zip() ist allerdings geringfügig schneller.
Ich benutze es gerne zip(*iterable)
(das ist der Code, nach dem Sie suchen) in meinen Programmen wie folgt:
def unzip(iterable):
return zip(*iterable)
ich finde unzip
besser lesbar.
>>> 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])
Gibt ein Tupel von Listen wie in der Frage an.
list1, list2 = [list(tup) for tup in zip(*original)]
Entpackt die beiden Listen.
Es ist zwar nur eine andere Möglichkeit, aber es hat mir sehr geholfen, also schreibe ich es hier:
Mit dieser Datenstruktur:
X=[1,2,3,4]
Y=['a','b','c','d']
XY=zip(X,Y)
Ergebend:
In: XY
Out: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
Der pythonischere Weg, es zu entpacken und zum Original zurückzukehren, ist meiner Meinung nach dieser:
x,y=zip(*XY)
Dies gibt jedoch ein Tupel zurück. Wenn Sie also eine Liste benötigen, können Sie Folgendes verwenden:
x,y=(list(x),list(y))
Naiver Ansatz
def transpose_finite_iterable(iterable):
return zip(*iterable) # `itertools.izip` for Python 2 users
funktioniert gut für endlich iterierbare (z. B.Sequenzen wie list
/tuple
/str
) von (potenziell unendlichen) Iterablen, die wie folgt dargestellt werden können
| |a_00| |a_10| ... |a_n0| |
| |a_01| |a_11| ... |a_n1| |
| |... | |... | ... |... | |
| |a_0i| |a_1i| ... |a_ni| |
| |... | |... | ... |... | |
Wo
n in ℕ
,a_ij
entsprichtj
-tes Element voni
-th iterierbar,
und nach der Bewerbung transpose_finite_iterable
wir bekommen
| |a_00| |a_01| ... |a_0i| ... |
| |a_10| |a_11| ... |a_1i| ... |
| |... | |... | ... |... | ... |
| |a_n0| |a_n1| ... |a_ni| ... |
Python-Beispiel für einen solchen Fall, wo 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)
Aber wir können es nicht verwenden transpose_finite_iterable
noch einmal, um zur ursprünglichen Struktur zurückzukehren iterable
Weil result
ist eine unendliche Iterable von endlichen Iterablen (tuple
s in unserem Fall):
>>> transpose_finite_iterable(result)
... hangs ...
Traceback (most recent call last):
File "...", line 1, in ...
File "...", line 2, in transpose_finite_iterable
MemoryError
Wie können wir also mit diesem Fall umgehen?
...und hier kommt das deque
Nachdem wir einen Blick auf die Dokumente von geworfen haben itertools.tee
Funktion, es gibt ein Python-Rezept, das mit einigen Modifikationen in unserem Fall hilfreich sein kann
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))
Lass uns das Prüfen
>>> 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
Synthese
Jetzt können wir eine allgemeine Funktion für die Arbeit mit Iterables von Iterables definieren, von denen einige endlich und andere potenziell unendlich sind functools.singledispatch
Dekorateur wie
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)
die als ihre eigene Umkehrung betrachtet werden kann (Mathematiker nennen diese Art von Funktionen). „Involutionen“) in der Klasse binärer Operatoren über endlichen nicht leeren Iterables.
Als Bonus von singledispatch
ing können wir bewältigen numpy
Arrays wie
import numpy as np
...
transpose.register(np.ndarray, np.transpose)
und es dann gerne nutzen
>>> array = np.arange(4).reshape((2,2))
>>> array
array([[0, 1],
[2, 3]])
>>> transpose(array)
array([[0, 2],
[1, 3]])
Notiz
Seit transpose
gibt Iteratoren zurück und wenn jemand einen haben möchte tuple
von list
Es ist wie in OP – dies kann zusätzlich mit gemacht werden map
eingebaute Funktion wie
>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
>>> tuple(map(list, transpose(original)))
(['a', 'b', 'c', 'd'], [1, 2, 3, 4])
Werbung
Ich habe eine allgemeine Lösung hinzugefügt lz
Paket aus 0.5.0
Version, die wie verwendet werden kann
>>> 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)]
P.S.
Es gibt keine (zumindest offensichtliche) Lösung für den Umgang mit potenziell unendlichen Iterables von potenziell unendlichen Iterables, dieser Fall kommt jedoch weniger häufig vor.
Da es Tupel zurückgibt (und jede Menge Speicher verbrauchen kann), ist das zip(*zipped)
Trick scheint mir eher clever als nützlich zu sein.
Hier ist eine Funktion, die Ihnen tatsächlich die Umkehrung von zip liefert.
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
original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
#unzip
a1 , a2 = zip(*original)
#make tuple with two list
result=(list(a1),list(a2))
result
result=(['a', 'b', 'c', 'd'], [1, 2, 3, 4])
Erwägen Sie die Verwendung 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]]
Keine der vorherigen Antworten effizient Bereitstellung der erforderlichen Ausgabe, nämlich a Tupel von Listen, eher als ein Liste der Tupel.Für Ersteres können Sie verwenden tuple
mit map
.Hier ist der Unterschied:
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])
Darüber hinaus gehen die meisten bisherigen Lösungen von Python 2.7 aus zip
gibt eine Liste und keinen Iterator zurück.
Für Python 3.x müssen Sie das Ergebnis an eine Funktion wie übergeben list
oder tuple
um den Iterator zu erschöpfen.Für speichereffiziente Iteratoren können Sie das Äußere weglassen list
Und tuple
fordert entsprechende Lösungen.
Während zip(*seq)
ist sehr nützlich, für sehr lange Sequenzen ist es möglicherweise ungeeignet, da es ein Tupel von Werten erstellt, die übergeben werden müssen.Ich habe zum Beispiel mit einem Koordinatensystem mit über einer Million Einträgen gearbeitet und finde es deutlich schneller, die Sequenzen direkt zu erstellen.
Ein allgemeiner Ansatz wäre etwa so:
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)
Doch je nachdem, was Sie mit dem Ergebnis machen möchten, kann die Wahl der Kollektion einen großen Unterschied machen.In meinem tatsächlichen Anwendungsfall ist die Verwendung von Sets und ohne interne Schleife deutlich schneller als alle anderen Ansätze.
Und wie andere angemerkt haben, kann es sinnvoll sein, stattdessen Numpy- oder Pandas-Sammlungen zu verwenden, wenn Sie dies mit Datensätzen tun.
So können Sie ein 2x4-Tupel in ein 4x2-Tupel transponieren.
>>> tuple(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))
Ergebnis
[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]