Pergunta

Eu tenho uma lista de tuplas de 2 itens e gostaria de convertê-las em 2 listas, onde a primeira contém o primeiro item de cada tupla e a segunda lista contém o segundo item.

Por exemplo:

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

Existe uma função interna que faz isso?

Foi útil?

Solução

zip é o seu próprio inverso!Desde que você use o operador especial *.

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

A maneira como isso funciona é ligando zip com os argumentos:

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

… exceto que os argumentos são passados ​​para zip diretamente (após ser convertido em uma tupla), portanto não há necessidade de se preocupar com o número de argumentos ficando muito grande.

Outras dicas

Você também poderia fazer

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

Isto deve escalar melhor.Especialmente se o Python conseguir não expandir a compreensão da lista, a menos que seja necessário.

(Aliás, ele forma duas tuplas (par) de listas, em vez de uma lista de tuplas, como zip faz.)

Se os geradores em vez das listas reais estiverem ok, isso faria o seguinte:

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

Os geradores não examinam a lista até que você solicite cada elemento, mas, por outro lado, mantêm referências à lista original.

Se você tiver listas que não tenham o mesmo comprimento, talvez não queira usar zip conforme a resposta de Patrick.Isso funciona:

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

Mas com listas de comprimentos diferentes, o zip trunca cada item para o comprimento da lista mais curta:

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

Você pode usar map sem função para preencher resultados vazios com None:

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

zip() é um pouco mais rápido.

eu gosto de usar zip(*iterable) (que é o trecho de código que você está procurando) em meus programas da seguinte forma:

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

eu acho unzip mais legível.

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

Fornece uma tupla de listas como na pergunta.

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

Descompacta as duas listas.

É apenas outra maneira de fazer isso, mas me ajudou muito, então escrevo aqui:

Tendo esta estrutura de dados:

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

Resultando em:

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

A maneira mais pitônica de descompactá-lo e voltar ao original é esta, na minha opinião:

x,y=zip(*XY)

Mas isso retorna uma tupla, então se você precisar de uma lista você pode usar:

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

Abordagem ingênua

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

funciona bem para iterável finito (por exemplosequências como list/tuple/str) de iteráveis ​​​​(potencialmente infinitos) que podem ser ilustrados como

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

onde

  • n in ℕ,
  • a_ij corresponde a j-º elemento de i-th iterável,

e depois de aplicar transpose_finite_iterable Nós temos

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

Exemplo Python de tal caso onde 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)

Mas não podemos usar transpose_finite_iterable novamente para retornar à estrutura original iterable porque result é um iterável infinito de iteráveis ​​finitos (tupleé no nosso caso):

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

Então, como podemos lidar com este caso?

...e aí vem o deque

Depois de darmos uma olhada nos documentos de itertools.tee função, existe uma receita Python que com algumas modificações pode ajudar no nosso 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 checar

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

Agora podemos definir uma função geral para trabalhar com iteráveis ​​de iteráveis, alguns dos quais são finitos e outros são 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 pode ser considerado como seu próprio inverso (os matemáticos chamam esse tipo de função "involuções") na classe de operadores binários sobre iteráveis ​​finitos e não vazios.


Como bônus de singledispatchpodemos lidar numpy matrizes como

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

e então use-o como

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

Observação

Desde transpose retorna iteradores e se alguém quiser ter um tuple de listé como no OP - isso pode ser feito adicionalmente com map função integrada como

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

Anúncio

Eu adicionei solução generalizada para lz pacote de 0.5.0 versão que pode ser usada 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)]

P.S.

Não há solução (pelo menos óbvia) para lidar com iteráveis ​​potencialmente infinitos de iteráveis ​​potencialmente infinitos, mas este caso é menos comum.

Como retorna tuplas (e pode usar muita memória), o zip(*zipped) truque parece mais inteligente do que útil, para mim.

Aqui está uma função que realmente fornecerá o inverso do 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 mais_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]]     

Nenhuma das respostas anteriores eficientemente fornecer a saída necessária, que é um tupla de listas, ao invés de um lista de tuplas.Para o primeiro, você pode usar tuple com map.Aqui está a diferença:

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

Além disso, a maioria das soluções anteriores assume Python 2.7, onde zip retorna uma lista em vez de um iterador.

Para Python 3.x, você precisará passar o resultado para uma função como list ou tuple para esgotar o iterador.Para iteradores com uso eficiente de memória, você pode omitir o externo list e tuple exige as respectivas soluções.

Enquanto zip(*seq) é muito útil, pode ser inadequado para sequências muito longas, pois criará uma tupla de valores a serem passados.Por exemplo, tenho trabalhado com um sistema de coordenadas com mais de um milhão de entradas e acho significativamente mais rápido criar as sequências diretamente.

Uma abordagem genérica seria algo assim:

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)

Mas, dependendo do que você quer fazer com o resultado, a escolha da coleção pode fazer muita diferença.No meu caso de uso real, usar conjuntos e nenhum loop interno é visivelmente mais rápido do que todas as outras abordagens.

E, como outros observaram, se você estiver fazendo isso com conjuntos de dados, pode fazer sentido usar coleções Numpy ou Pandas.

É assim que você pode transpor uma tupla 2x4 para uma tupla 4x2.

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

resultado

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top