2008-08-21 12 views
342

Mam listę 2-elementowych krotek i chciałbym przekonwertować je na 2 listy, gdzie pierwsza zawiera pierwszy element w każdej krotce, a druga zawiera drugi element.Funkcja transpozycji/rozpakowania (odwrotność zip)?

Na przykład:

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

Czy istnieje wbudowana funkcja, która to robi?

+3

Doskonałe odpowiedzi poniżej, ale zobacz też [transpozycja numpy] (http://docs.scipy.org/doc/numpy/reference/generated/numpy.transpose.html) – opyate

+1

Zobacz tę fajną odpowiedź, aby zrobić to samo z generatory zamiast listy: [how-to-unzip-an-iterator] (http://stackoverflow.com/questions/30805000/how-to-unzip-an-iterator) – YvesgereY

Odpowiedz

546

zip jest odwrotnością! Pod warunkiem użycia specjalnego operatora *.

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

Sposób ten działa to poprzez wywołanie zip z argumentami:

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

... oprócz argumentów przekazywanych do zip bezpośrednio (po konwertowane na krotki), więc nie trzeba się martwić o liczbie argumentów, które stają się zbyt duże.

+6

Och, gdyby tylko to było takie proste. Rozpakowanie 'zip ([], [])' w ten sposób nie dostaniesz '[], []'. Dostaje cię '[]'. Jeśli tylko ... – user2357112

+0

@ user2357112 daje 'zip (* zip ([lista1], [lista2]))' daje '([lista1, lista2])'. – cdhagmann

+0

@cdhagmann: 'zip ([lista1], [lista2])' nigdy nie jest tym, czego potrzebujesz. To daje ci '[(list1, list2)]'. – user2357112

22

Można również zrobić skalę

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

It powinien lepiej. Zwłaszcza jeśli Python radzi sobie dobrze, nie rozszerzając listy, chyba że jest to konieczne.

(Nawiasem mówiąc, to sprawia, że ​​2-krotki (para) list, zamiast listę krotek, jak zip robi.)

Jeśli generatory zamiast rzeczywistych list są ok, to byłoby to zrobić:

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

Generatory nie przeżuwają listy, dopóki nie poprosi o każdy element, ale z drugiej strony zachowują odniesienia do oryginalnej listy.

+6

"Zwłaszcza jeśli Python dobrze sobie radzi, nie rozszerzając listy, chyba że jest to konieczne." mmm ... normalnie, zrozumienie list jest natychmiast rozszerzane - czy coś złego? – glglgl

+0

@glglgl: Nie, prawdopodobnie masz rację. Miałem tylko nadzieję, że jakaś przyszła wersja może zacząć robić właściwe rzeczy. (Zmiana nie jest niemożliwa, semantyka efektów ubocznych, która wymaga zmian, prawdopodobnie już jest odradzana). –

+8

To, co masz nadzieję uzyskać, to ekspresja generatora - która już istnieje. – glglgl

19

Jeśli masz listy, które nie mają tej samej długości, możesz nie chcieć używać zip zgodnie z odpowiedzią Patricks. To działa:

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

Ale z różnych list długości, zip obcina każdą pozycję do długości najkrótszej listy:

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

można wykorzystać mapę bez funkcji do wypełnienia pustych wyniki z None:

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

zip() jest jednak marginalnie szybszy.

+3

ciekawe, czy możesz wyjaśnić, jak działa 'map'? –

+4

Można również użyć 'izip_longest' – Marcin

+2

Znany jako' zip_longest' dla użytkowników python3. – zezollo

11

Lubię używać zip(*iterable) (który jest kawałek kodu szukasz) w moich programach jak tak:

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

znajdę unzip bardziej czytelny.

2

To tylko kolejny sposób, aby to zrobić, ale to bardzo mi pomogło więc piszę tutaj:

Mając tę ​​strukturę danych:

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

skutkuje:

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

The bardziej pythonic sposób rozpakowania go i powrotu do oryginału to ta w mojej opinii:

x,y=zip(*XY) 

Ale ten powrót krotki więc jeśli trzeba tablicę można użyć:

xy=(list(x),list(y)) 
7
>>> 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]) 

Daje krotki list jak w pytaniu.

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

Rozpakowuje dwie listy.