Transposer/Décompressez Fonction (l'inverse de zip)?
j'ai une liste de tuples à 2 éléments et j'aimerais les convertir en 2 listes où le premier contient le premier élément dans chaque tuple et la deuxième liste contient le deuxième élément.
par exemple:
original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
# and I want to become...
result = (['a', 'b', 'c', 'd'], [1, 2, 3, 4])
y a-t-il une fonction intégrée qui fait cela?
11 réponses
zip
est son propre inverse! À condition que vous utilisez le spécial * l'opérateur.
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)])
[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
la façon dont cela fonctionne est en appelant zip
avec les arguments:
zip(('a', 1), ('b', 2), ('c', 3), ('d', 4))
... sauf que les arguments sont passés à zip
directement (après avoir été converti en un tuple), donc il n'y a pas besoin de s'inquiéter du nombre d'arguments devenant trop grand.
, Vous pouvez aussi le faire
result = ([ a for a,b in original ], [ b for a,b in original ])
Il devrait échelle de mieux. Surtout si Python fait du bien sur le fait de ne pas étendre les compréhensions de liste à moins que nécessaire.
(soit dit en passant, il fait un 2-tuple (paire) de listes, plutôt qu'une liste de tuples, comme zip
fait.)
si les générateurs au lieu des listes actuelles sont ok, cela ferait cela:
result = (( a for a,b in original ), ( b for a,b in original ))
les générateurs ne mâchez pas la liste jusqu'à ce que vous demandiez pour chaque élément, mais d'un autre côté, ils gardent des références à la liste originale.
si vous avez des listes qui ne sont pas de la même longueur, vous pouvez ne pas vouloir utiliser zip que selon Patricks réponse. Cela fonctionne:
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)])
[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
mais avec des listes de longueur différentes, zip tronque chaque élément à la longueur de la liste la plus courte:
>>> zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e')]
vous pouvez utiliser map sans fonction pour remplir des résultats vides avec None:
>>> map(None, *[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e'), (1, 2, 3, 4, None)]
zip () est légèrement plus rapide cependant.
j'aime utiliser zip(*iterable)
(qui est le morceau de code que vous recherchez) dans mes programmes comme suit:
def unzip(iterable):
return zip(*iterable)
je trouve unzip
plus lisible.
>>> 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])
donne un tuple de listes comme dans la question.
list1, list2 = [list(tup) for tup in zip(*original)]
Déballe les deux listes.
C'est seulement une autre façon de le faire mais ça m'a beaucoup aidé alors je l'écris ici:
ayant cette structure de données:
X=[1,2,3,4]
Y=['a','b','c','d']
XY=zip(X,Y)
résultant en:
In: XY
Out: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
la façon plus pythonique de le décompresser et de revenir à l'original est celle-ci à mon avis:
x,y=zip(*XY)
mais ce retour un tuple donc si vous avez besoin d'un tableau, vous pouvez utiliser:
xy=(list(x),list(y))
Puisqu'il renvoie des tuples (et peut utiliser des tonnes de mémoire), le zip(*zipped)
tour semble plus intelligent que utile, pour moi.
Voici une fonction qui vous donnera l'inverse de 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
aucune des réponses précédentes efficacement fournir la sortie requise, qui est un tuple de listes , plutôt qu'une liste de tuples . Pour le premier, vous pouvez utiliser tuple
avec map
. Voici la différence:
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])
de plus, la plupart des solutions précédentes supposent Python 2.7, où zip
renvoie une liste plutôt qu'un itérateur.
Pour Python 3.x, vous aurez besoin de passer le résultat à une fonction telle que list
ou tuple
pour épuiser l'itérateur. Pour les itérateurs économes en mémoire, vous pouvez omettre les appels externes list
et tuple
pour les solutions respectives.
bien que zip(*seq)
soit très utile, il peut ne pas convenir pour de très longues séquences car il va créer un nuage de valeurs à passer. Par exemple, j'ai travaillé avec un système de coordonnées avec plus d'un million d'entrées et je le trouve nettement plus rapide pour créer les séquences directement.
Une approche générique serait quelque chose comme ceci:
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)
Mais, selon ce que vous voulez faire avec le résultat, le choix de collection peuvent faire une grande différence. Dans mon cas d'utilisation, à l'aide de jeux et pas de boucle interne, est sensiblement plus rapide que toutes les autres approches.
et, comme d'autres l'ont fait remarquer, si vous faites cela avec des ensembles de données, il pourrait être logique d'utiliser des collections Numpy ou Pandas à la place.
une autre façon de penser à unzip
ou transpose
est de convertir une liste de lignes en une liste de colonnes.
pitchers = [('Nolan', 'Ryan'),
('Roger', 'Clements'),
('Schilling','Curt')]
first_names, last_names = zip(*pitchers)
In [45]: first_names
Out[45]: ('Nolan', 'Roger', 'Schilling')
In [46]: last_names
Out[46]: ('Ryan', 'Clements', 'Curt')
c'est comme ça qu'on peut transposer un tuple 2x4 en un tuple 4x2.
>>> tuple(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))
résultat
[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]