Domanda

Ho un elenco di tuple da 2 elementi e vorrei convertirli in 2 elenchi in cui il primo contiene il primo elemento in ciascuna tupla e il secondo elenco contiene il secondo elemento.

Per esempio:

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

Esiste una funzione integrata che lo fa?

È stato utile?

Soluzione

zip è il suo inverso!A patto di utilizzare l'operatore speciale *.

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

Il modo in cui funziona è chiamando zip con le argomentazioni:

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

… tranne che gli argomenti vengono passati a zip direttamente (dopo essere stato convertito in una tupla), quindi non è necessario preoccuparsi che il numero di argomenti diventi troppo grande.

Altri suggerimenti

Potresti anche farlo

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

Esso Dovrebbe scalare meglio.Soprattutto se Python riesce a non espandere la comprensione dell'elenco se non necessario.

(Per inciso, crea una lista di 2 tuple (coppia) invece che una lista di tuple, come zip fa.)

Se i generatori invece degli elenchi effettivi fossero ok, questo farebbe questo:

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

I generatori non sfogliano l'elenco finché non viene richiesto ciascun elemento, ma d'altro canto mantengono i riferimenti all'elenco originale.

Se disponi di elenchi che non hanno la stessa lunghezza, potresti non voler utilizzare zip come da risposta di Patrick.Funziona:

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

Ma con elenchi di lunghezza diversa, zip tronca ogni elemento alla lunghezza dell'elenco più breve:

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

Puoi utilizzare la mappa senza funzione per riempire i risultati vuoti con Nessuno:

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

zip() è leggermente più veloce però.

Mi piace usare zip(*iterable) (che è il pezzo di codice che stai cercando) nei miei programmi in questo modo:

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

io trovo unzip più leggibile.

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

Fornisce una tupla di elenchi come nella domanda.

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

Decomprime i due elenchi.

È solo un altro modo per farlo ma mi ha aiutato molto quindi lo scrivo qui:

Avendo questa struttura dati:

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

Con il risultato di:

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

Il modo più Python per decomprimerlo e tornare all'originale è questo secondo me:

x,y=zip(*XY)

Ma questo restituisce una tupla quindi se hai bisogno di una lista puoi usare:

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

Approccio ingenuo

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

funziona bene per iterabili finiti (ad es.sequenze simili list/tuple/str) di iterabili (potenzialmente infiniti) che possono essere illustrati come

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

Dove

  • n in ℕ,
  • a_ij corrisponde a j-esimo elemento di i-esimo iterabile,

e dopo l'applicazione transpose_finite_iterable noi abbiamo

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

Esempio Python di questo caso in cui 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)

Ma non possiamo usarlo transpose_finite_iterable di nuovo per tornare alla struttura originale iterable Perché result è un iterabile infinito di iterabili finiti (tuples nel nostro caso):

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

Allora come possiamo affrontare questo caso?

...ed ecco che arriva il deque

Dopo aver dato un'occhiata ai documenti di itertools.tee funzione, esiste una ricetta Python che con qualche modifica può aiutare nel nostro 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))

controlliamo

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

Sintesi

Ora possiamo definire una funzione generale per lavorare con iterabili di iterabili di cui uno finito e un altro potenzialmente infinito utilizzando functools.singledispatch decoratore Piace

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)

che può essere considerata come il suo inverso (i matematici chiamano questo tipo di funzioni "involuzioni") nella classe degli operatori binari su iterabili finiti non vuoti.


Come bonus di singledispatching che possiamo gestire numpy array come

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

e poi usarlo come

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

Nota

Da transpose restituisce gli iteratori e se qualcuno vuole avere un file tuple Di listÈ come in OP: questo può essere fatto in aggiunta con map funzione incorporata Piace

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

Annuncio

Ho aggiunto una soluzione generalizzata a lz pacchetto da 0.5.0 versione che può essere utilizzata come

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

Non esiste una soluzione (almeno ovvia) per gestire iterabili potenzialmente infiniti di iterabili potenzialmente infiniti, ma questo caso è tuttavia meno comune.

Poiché restituisce tuple (e può utilizzare tonnellate di memoria), il file zip(*zipped) Il trucco mi sembra più intelligente che utile.

Ecco una funzione che ti darà effettivamente l'inverso di 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

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

Considera l'utilizzo more_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]]     

Nessuna delle risposte precedenti in modo efficiente fornire l'output richiesto, ovvero a tupla di elenchi, piuttosto che a elenco di tuple.Per il primo, puoi usare tuple con map.Ecco la differenza:

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

Inoltre, la maggior parte delle soluzioni precedenti presuppone Python 2.7, dove zip restituisce un elenco anziché un iteratore.

Per Python 3.x, dovrai passare il risultato a una funzione come list O tuple per esaurire l'iteratore.Per gli iteratori efficienti in termini di memoria, è possibile omettere l'outer list E tuple chiede le rispettive soluzioni.

Mentre zip(*seq) è molto utile, potrebbe non essere adatto per sequenze molto lunghe poiché creerà una tupla di valori da passare.Ad esempio, lavoro con un sistema di coordinate con oltre un milione di voci e trovo che sia molto più veloce creare direttamente le sequenze.

Un approccio generico sarebbe qualcosa del genere:

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)

Ma, a seconda di cosa vuoi fare con il risultato, la scelta della collezione può fare una grande differenza.Nel mio caso d'uso reale, l'utilizzo di set e nessun ciclo interno è notevolmente più veloce di tutti gli altri approcci.

E, come altri hanno notato, se lo fai con i set di dati, potrebbe avere senso utilizzare invece le raccolte Numpy o Pandas.

Ecco come puoi trasporre una tupla 2x4 in una tupla 4x2.

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

risultato

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top