Question

J'ai une liste de 2-élément n-uplets et je voudrais les convertir en 2 listes dont le premier contient le premier élément de chaque n-uplet 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])

Est-il intégré la fonction qui fait ça?

Était-ce utile?

La solution

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 à un n-uplet), donc il n'y a pas besoin de s'inquiéter sur le nombre d'arguments devient trop grand.

Autres conseils

Vous pouvez également faire de

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

Il devrait l'échelle de mieux.Surtout si Python fait bien sur pas d'allonger la liste des compréhensions sauf si nécessaire.

(En passant, il fait une 2-tuple (paire) de listes, plutôt qu'une liste de tuples, comme zip n'.)

Si les générateurs au lieu de listes réelles sont ok, ce serait de faire:

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

Les générateurs de ne pas grignoter dans la liste jusqu'à ce que vous demandez pour chaque élément, mais d'un autre côté, ils ne conserver les références à la liste d'origine.

Si vous avez des listes qui ne sont pas de la même longueur, vous ne voulez pas utiliser zip comme par Patricks réponse.Ceci fonctionne:

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

Mais avec des longueurs différentes listes, zip tronque la valeur de chaque élément de la longueur de la plus courte liste:

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

Vous pouvez utiliser la carte avec aucune fonction à remplir les vides résultats avec Aucun:

>>> 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 que.

J'aime utiliser zip(*iterable) (ce qui est le morceau de code que vous cherchez) dans mes programmes, comme ça:

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 n-uplet 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 il m'a beaucoup aidé, donc je l'écris ici:

Ayant cette structure de données:

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

Ce qui entraîne:

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

Le plus pythonic moyen de décompresser et de revenir en arrière à l'original est cela, à mon avis:

x,y=zip(*XY)

Mais ce retour à un tuple donc, si vous avez besoin d'une liste, vous pouvez utiliser:

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

Approche naïve

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

fonctionne très bien pour les finis itérable (par ex.les séquences comme list/tuple/str) de (potentiellement infinie) iterables qui peut être illustré comme

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

  • n in ℕ,
  • a_ij correspond à j-ème élément de i-th itératif,

et après l'application de transpose_finite_iterable nous obtenons

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

Python exemple de cas où 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)

Mais nous ne pouvons pas utiliser transpose_finite_iterable pour revenir à la structure de l'original iterable parce que result est infini et l'objet iterable finis iterables (tuples dans notre cas):

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

Alors, comment pouvons-nous traiter ce cas?

...et voici le deque

Après, nous prenons un coup d'oeil à docs de itertools.tee la fonction, il est Python recette avec quelques modifications peuvent aider dans notre cas

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))

nous allons vérifier

>>> 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

Synthèse

Maintenant, nous pouvons définir la fonction générale pour travailler avec iterables de iterables ceux qui sont finis et l'autre sont potentiellement infini à l'aide de functools.singledispatch décorateur comme

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)

ce qui peut être considéré comme son propre inverse (les mathématiciens appellent ce genre de fonctions "involutions"") dans la catégorie des opérateurs binaires sur les corps finis non vides iterables.


Comme un bonus de singledispatching nous pouvons nous occuper de numpy des tableaux comme

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

et puis l'utiliser comme

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

Note

Depuis transpose retourne les itérateurs et si quelqu'un veut avoir un tuple de lists comme dans l'OP -- ce qui peut être fait en outre avec map fonction intégrée comme

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

Annonce

J'ai ajouté généralisée solution lz package à partir de 0.5.0 version qui peut être utilisée comme

>>> 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.

Il n'y a pas de solution (à moins évident) pour le traitement potentiellement infinie objet iterable potentiellement infinie iterables, mais ce cas est moins bien commun.

Depuis il renvoie les n-uplets (et peut utiliser des tonnes de mémoire), le zip(*zipped) astuce semble plus intelligent que utile, à moi.

Voici une fonction qui va vraiment vous donner 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
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

résultat=(['a', 'b', 'c', 'd'], [1, 2, 3, 4])

Pensez à utiliser more_itertools.décompressez:

>>> 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]]     

Aucune des réponses précédentes efficacement fournir le nécessaire à la sortie d'un tuple de listes, plutôt qu'une liste de tuples.Pour l'ex, 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])

En outre, 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 l'évacuation de l'itérateur.Pour efficace de la mémoire des itérateurs, vous pouvez omettre l'extérieur list et tuple les appels pour les solutions respectives.

Alors que zip(*seq) est très utile, il peut être inappropriés pour les très longues séquences comme il va créer un tuple de valeurs à transmettre dans.Par exemple, j'ai travaillé avec un système de coordonnées avec plus d'un million d'entrées et de trouver signifcantly 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 la collection peuvent faire une grande différence.Dans mon cas d'utilisation, à l'aide de jeux et de n de la boucle interne, est sensiblement plus rapide que toutes les autres approches.

Et, comme d'autres l'ont noté, si vous faites cela avec jeux de données, il pourrait être judicieux d'utiliser Numpy ou les Pandas les collections de la place.

C'est comment vous pouvez transposer un 2x4 tuple dans un 4x2 n-uplet.

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

résultat

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top