Pregunta

Tengo una lista de tuplas de 2 elementos y me gustaría convertirlas en 2 listas donde la primera contiene el primer elemento de cada tupla y la segunda lista contiene el segundo elemento.

Por ejemplo:

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

¿Existe una función incorporada que haga eso?

¿Fue útil?

Solución

zip ¡Es su propio inverso!Siempre que utilice el operador especial *.

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

La forma en que esto funciona es llamando zip con los argumentos:

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

... excepto que los argumentos se pasan a zip directamente (después de convertirse en una tupla), por lo que no hay necesidad de preocuparse de que la cantidad de argumentos sea demasiado grande.

Otros consejos

También podrías hacer

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

Él debería escalar mejor.Especialmente si Python logra no expandir la lista por comprensión a menos que sea necesario.

(Por cierto, crea una lista de 2 tuplas (par), en lugar de una lista de tuplas, como zip hace.)

Si los generadores en lugar de las listas reales están bien, esto haría lo siguiente:

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

Los generadores no analizan la lista hasta que usted pregunta por cada elemento, pero, por otro lado, mantienen referencias a la lista original.

Si tiene listas que no tienen la misma longitud, es posible que no desee utilizar zip según la respuesta de Patrick.Esto funciona:

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

Pero con listas de diferentes longitudes, zip trunca cada elemento a la longitud de la lista más corta:

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

Puede usar un mapa sin función para llenar resultados vacíos con Ninguno:

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

Sin embargo, zip() es un poco más rápido.

me gusta usar zip(*iterable) (que es el fragmento de código que estás buscando) en mis programas de la siguiente manera:

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

Encuentro unzip más legible.

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

Da una tupla de listas como en la pregunta.

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

Desempaqueta las dos listas.

Es sólo otra forma de hacerlo pero me ayudó mucho así que lo escribo aquí:

Teniendo esta estructura de datos:

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

Resultando en:

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

En mi opinión, la forma más pitónica de descomprimirlo y volver al original es esta:

x,y=zip(*XY)

Pero esto devuelve una tupla, por lo que si necesita una lista puede usar:

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

Enfoque ingenuo

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

funciona bien para iterables finitos (p. ej.secuencias como list/tuple/str) de iterables (potencialmente infinitos) que se pueden ilustrar como

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

dónde

  • n in ℕ,
  • a_ij corresponde a j-ésimo elemento de i-ésimo iterable,

y después de aplicar transpose_finite_iterable obtenemos

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

Ejemplo de Python de tal caso donde 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)

Pero no podemos usar transpose_finite_iterable nuevamente para regresar a la estructura del original iterable porque result es un iterable infinito de iterables finitos (tuples en nuestro caso):

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

Entonces, ¿cómo podemos abordar este caso?

...y aquí viene el deque

Después de echar un vistazo a los documentos de itertools.tee función, existe una receta de Python que con algunas modificaciones puede ayudar en nuestro caso

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

vamos a revisar

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

Síntesis

Ahora podemos definir una función general para trabajar con iterables de iterables, algunos de los cuales son finitos y otros son potencialmente infinitos usando functools.singledispatch decorador como

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)

que puede considerarse como su propia inversa (los matemáticos llaman a este tipo de funciones "involuciones") en clase de operadores binarios sobre iterables finitos no vacíos.


Como bonificación de singledispatchpodemos manejar numpy matrices como

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

y luego usarlo como

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

Nota

Desde transpose devuelve iteradores y si alguien quiere tener un tuple de listEs como en OP: esto se puede hacer adicionalmente con map función incorporada como

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

Anuncio

Agregué una solución generalizada a lz paquete de 0.5.0 versión que se puede utilizar como

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

PD

No existe una solución (al menos obvia) para manejar iterables potencialmente infinitos de iterables potencialmente infinitos, pero este caso es menos común.

Dado que devuelve tuplas (y puede utilizar toneladas de memoria), el zip(*zipped) El truco me parece más inteligente que útil.

Aquí hay una función que en realidad le dará la inversa 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

resultado=(['a', 'b', 'c', 'd'], [1, 2, 3, 4])

Considere usar more_itertools.descomprimir:

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

Ninguna de las respuestas anteriores eficientemente proporcionar el resultado requerido, que es un tupla de listas, preferible a lista de tuplas.Para el primero, puedes usar tuple con map.Aquí está la diferencia:

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

Además, la mayoría de las soluciones anteriores asumen Python 2.7, donde zip devuelve una lista en lugar de un iterador.

Para Python 3.x, deberá pasar el resultado a una función como list o tuple para agotar el iterador.Para iteradores con uso eficiente de la memoria, puede omitir el exterior list y tuple exige las soluciones respectivas.

Mientras zip(*seq) es muy útil, puede que no sea adecuado para secuencias muy largas ya que creará una tupla de valores para pasar.Por ejemplo, he estado trabajando con un sistema de coordenadas con más de un millón de entradas y encuentro que es mucho más rápido crear las secuencias directamente.

Un enfoque genérico sería algo como esto:

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)

Pero, dependiendo de lo que quieras hacer con el resultado, la elección de la colección puede marcar una gran diferencia.En mi caso de uso real, usar conjuntos y sin bucle interno es notablemente más rápido que todos los demás enfoques.

Y, como otros han señalado, si hace esto con conjuntos de datos, podría tener sentido usar colecciones Numpy o Pandas.

Así es como puedes transponer una tupla de 2x4 a una tupla de 4x2.

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

resultado

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top