Transpor / Descompacte Função (inverso do fecho de correr)?

votos
363

Eu tenho uma lista de tuplas 2-item e gostaria de convertê-los em 2 listas onde a primeira contém o primeiro item em cada tupla ea segunda lista deté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?

Publicado 21/08/2008 em 05:29
fonte usuário
Em outras línguas...                            


14 respostas

votos
576

zipé sua própria inversa! 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 forma como isto funciona é chamando zipcom os argumentos:

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

... exceto os argumentos são passados para zipdiretamente (depois de ser convertido em uma tupla), por isso não há necessidade de se preocupar com o número de argumentos ficando muito grande.

Respondeu 21/08/2008 em 05:36
fonte usuário

votos
24

Você também pode fazer

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

Ele deve dimensionar melhor. Especialmente se Python faz bem em não ampliar as compreensões lista, excepto se necessário.

(Aliás, ele faz uma 2-tupla (par) de listas, em vez de uma lista de tuplas, como zipo faz.)

Se geradores em vez de listas reais são ok, isso faria isso:

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

Os geradores não arrebentar com a lista até você perguntar para cada elemento, mas, por outro lado, eles manter as referências à lista original.

Respondeu 24/08/2008 em 18:07
fonte usuário

votos
19

Se você tem listas que não são do mesmo tamanho, você não pode querer usar zip como por Patricks resposta. Isso funciona:

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

Mas com diferentes listas de comprimento, 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 o mapa com nenhuma 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 () é ligeiramente mais rápido embora.

Respondeu 02/01/2011 em 13:14
fonte usuário

votos
12

Eu gosto de usar zip(*iterable)(que é a parte do código que você está procurando) em meus programas assim:

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

Eu acho unzipmais legível.

Respondeu 01/03/2014 em 16:00
fonte usuário

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

Dá uma tupla de listas como na questão.

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

Descompacta as duas listas.

Respondeu 05/03/2016 em 11:08
fonte usuário

votos
2

abordagem ingênua

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

funciona bem para iteráveis finito (por exemplo, sequências como list/ tuple/ str) de iterables (potencialmente infinitas) que pode ser ilustrada como

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

Onde

  • n in ℕ,
  • a_ijcorresponde a j-TH elemento de iiteráveis -ésima,

e depois de aplicar transpose_finite_iterableobtemos

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

Python exemplo de tal caso 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_iterablenovamente para retornar à estrutura de originais iterable, porque resulté um iterable infinito de iterables finitos ( tuples 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 aqui vem o deque

Depois de dar uma olhada em docs de itertools.teefunção , há Python receita que com algumas modificações podem 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 a função geral para trabalhar com iterables de iterables aqueles dos quais são finitos e mais queridos são potencialmente infinito usando functools.singledispatchdecorador 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 a sua própria inversa (os matemáticos chamam este tipo de funções "involuções" ) na classe de operadores binários mais iterables não vazios finitos.


Como um bônus de singledispatching podemos lidar com numpymatrizes como

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

e, em seguida, usá-lo como

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

Nota

Desde transposeretorna iteradores e se alguém quer ter um tuplede lists como em OP - isso pode ser feito também com mapfunção interna como

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

Propaganda

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

PS

Não existe solução (pelo menos óbvia) para manuseamento de iteráveis ​​potencialmente infinito de iterables potencialmente infinitas, mas neste caso, é menos comum, embora.

Respondeu 21/12/2018 em 12:46
fonte usuário

votos
2

É apenas uma outra maneira de fazê-lo, mas ele me ajudou muito para que eu escreva 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 Python para descompactá-lo e voltar para o original é este em minha opinião:

x,y=zip(*XY)

Mas isso retornar uma tupla por isso, se você precisa de uma matriz que você pode usar:

xy=(list(x),list(y))
Respondeu 26/01/2016 em 10:45
fonte usuário

votos
1

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

Aqui está uma função que vai realmente dar-lhe o inverso 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
Respondeu 11/06/2018 em 13:35
fonte usuário

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

Respondeu 12/04/2019 em 03:18
fonte usuário

votos
0

Considere o uso de more_itertools.unzip .

Respondeu 02/01/2019 em 21:30
fonte usuário

votos
0

Enquanto zip(*seq)é muito útil, pode não ser adequado para as sequências muito longas, uma vez que irá criar uma tupla de valores a serem passados. Por exemplo, eu tenho trabalhado com um sistema de coordenadas com mais de um milhão de entradas e encontrá-lo signifcantly mais rápido para criar as sequências directamente.

A abordagem genérica seria algo como isto:

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 de coleção pode fazer uma grande diferença. No meu caso uso real, usando conjuntos e nenhum loop interno, é visivelmente mais rápido do que todas as outras abordagens.

E, como outros já registou, se você está fazendo isso com conjuntos de dados, pode fazer sentido usar coleções Numpy ou pandas em seu lugar.

Respondeu 26/09/2018 em 14:08
fonte usuário

votos
0

Nenhuma das respostas anteriores eficiente fornecer a saída necessária, o que é uma tupla de listas , em vez de uma lista de tuplas . Para o primeiro, você pode usar tuplecom 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 assumem Python 2.7, onde zipretorna uma lista de mais do que uma iteração.

Para Python 3.x, você precisará passar o resultado para uma função como listou tupleesgotar o iterador. Para iterators memória-eficiente, você pode omitir o exterior liste tupleapela a que as respectivas soluções.

Respondeu 23/08/2018 em 17:36
fonte usuário

votos
0

Outra maneira de pensar sobre unzipou transposeestá convertendo uma lista de linhas em uma lista de colunas.

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')
Respondeu 18/04/2018 em 02:27
fonte usuário

votos
-1

Isto é como você pode transpor uma tupla 2x4 em uma tupla 4x2.

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

resultado

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Respondeu 30/06/2018 em 03:23
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more