Transponere / Pakk-funksjon (inverse av zip)?

stemmer
377

Jeg har en liste over to-element tupler og jeg ønsker å konvertere dem til 2 lister der det første inneholder den første elementet i hver tuppel og den andre listen holder det andre elementet.

For eksempel:

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

Er det en innebygd funksjon som gjør det?

Publisert på 21/08/2008 klokken 04:29
kilden bruker
På andre språk...                            


14 svar

stemmer
603

ziper sin egen invers! Forutsatt at du bruker den spesielle * operatøren.

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

Måten dette fungerer på er ved å ringe zipmed argumentene:

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

... bortsett argumentene er gått til zipdirekte (etter å ha blitt konvertert til et tuppel), så det er ingen grunn til å bekymre seg om antall argumenter å bli for stor.

Svarte 21/08/2008 kl. 04:36
kilden bruker

stemmer
25

Du kan også gjøre

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

Det bør skalere bedre. Spesielt hvis Python gjør god på å ikke utvide listen oppfattelser mindre nødvendig.

(Forresten, det gjør en 2-tuppel (par) av lister, snarere enn en liste med tupler, som zipgjør det.)

Hvis generatorer i stedet for faktiske listene er ok, vil dette gjøre det:

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

Generatorene ikke knaske gjennom listen til du be om hvert element, men på den annen side, kan de holde referanser til den opprinnelige listen.

Svarte 24/08/2008 kl. 17:07
kilden bruker

stemmer
19

Hvis du har lister som ikke er like lange, kan du ikke ønsker å bruke zip som per Patricks svar. Dette fungerer:

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

Men med ulike lengde lister, avkorter zip hvert element til lengden av den korteste listen:

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

Du kan bruke kartet uten funksjon for å fylle tomme resultater med Ingen:

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

zip () er marginalt raskere skjønt.

Svarte 02/01/2011 kl. 12:14
kilden bruker

stemmer
12

Jeg liker å bruke zip(*iterable)(som er den del av koden du leter etter) i mine programmer som så:

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

Jeg finner unzipmer lesbar.

Svarte 01/03/2014 kl. 15:00
kilden bruker

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

Gir en tuppel av lister som i spørsmålet.

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

Pakker de to listene.

Svarte 05/03/2016 kl. 11:08
kilden bruker

stemmer
2

naive tilnærming

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

fungerer bra for endelig iterable (f.eks sekvenser som list/ tuple/ str) av (potensielt uendelig) iterables som kan illustreres som

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

hvor

  • n in ℕ,
  • a_ijtilsvarer j'te element av i-th iterable,

og etter påføring transpose_finite_iterablefår vi

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

Python eksempel på et slikt tilfelle hvor 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)

Men vi kan ikke bruke transpose_finite_iterableigjen for å gå tilbake til strukturen i originalen iterablefordi resulter en uendelig iterable av endelige iterables ( tuples i vårt tilfelle):

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

Så hvordan kan vi håndtere denne saken?

... og her kommer deque

Etter at vi ta en titt på docs av itertools.teefunksjon , er det Python oppskrift som med litt modifisering kan bidra i vårt tilfelle

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

la oss sjekke

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

syntese

Nå kan vi definere generell funksjon for å arbeide med iterables av iterables de som er endelig, og den som er potensielt uendelig hjelp functools.singledispatchdekoratør som

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)

som kan betraktes som en egen invers (matematikere kaller denne typen funksjoner "involutions" ) i klasse binære operatører over endelige ikke-tomme iterables.


Som en bonus på singledispatching vi kan håndtere numpymatriser som

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

og deretter bruke den som

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

Merk

Siden transposereturnerer iteratorer og hvis noen ønsker å ha en tupleav lists som i OP - dette kan gjøres i tillegg med mapinnebygd funksjon som

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

Annonse

Jeg har lagt generalisert løsningen for å lzpakke fra 0.5.0versjon som kan brukes som

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

Det er ingen løsning (minst åpenbare) for håndtering av potensielt uendelig iterable av potensielt uendelig iterables, men dette tilfellet er mindre vanlig skjønt.

Svarte 21/12/2018 kl. 12:46
kilden bruker

stemmer
1

Siden den returnerer tupler (og kan bruke tonnevis av minne), den zip(*zipped)virker trikset mer avanserte enn nyttig for meg.

Her er en funksjon som faktisk vil gi deg den inverse av 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
Svarte 11/06/2018 kl. 13:35
kilden bruker

stemmer
1

Det er bare en annen måte å gjøre det, men det hjalp meg mye så jeg skriver det her:

Å ha denne datastruktur:

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

Resulterer i:

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

Jo mer Pytonske måte å pakke den ut og gå tilbake til den opprinnelige er dette en etter min mening:

x,y=zip(*XY)

Men dette returnere en tuppel så hvis du trenger en matrise kan du bruke:

xy=(list(x),list(y))
Svarte 26/01/2016 kl. 10:45
kilden bruker

stemmer
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

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

Svarte 12/04/2019 kl. 03:18
kilden bruker

stemmer
0

Vurder å bruke more_itertools.unzip .

Svarte 02/01/2019 kl. 21:30
kilden bruker

stemmer
0

Selv zip(*seq)er svært nyttig, kan det være uegnet for svært lange sekvenser som det vil skape en tuppel av verdier som skal sendes i. For eksempel har jeg jobbet med et koordinatsystem med over en million oppføringer og synes det signifcantly raskere å lage sekvensene direkte.

Et generisk tilnærming ville være noe sånt som dette:

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)

Men, avhengig av hva du vil gjøre med resultatet, kan valget av samlingen gjøre en stor forskjell. I min faktiske bruken tilfelle, ved hjelp av sett og ingen intern loop, er merkbart raskere enn alle andre tilnærminger.

Og som andre har nevnt, hvis du gjør dette med datasett, kan det være fornuftig å bruke Numpy eller Pandas samlinger i stedet.

Svarte 26/09/2018 kl. 14:08
kilden bruker

stemmer
0

Ingen av de tidligere svarene effektivt gi den nødvendige effekt, som er et tuppel av lister , snarere enn en liste med tupler . For det første, kan du bruke tuplemed map. Her er forskjellen:

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

I tillegg er de fleste av de tidligere løsningene anta Python 2.7, der zipreturneres en liste i stedet for i en iteratorblokk.

For Python 3.x, må du passere resultatet til en funksjon, for eksempel list, eller tupleå utmatte iterator. For minneeffektive iteratorer, kan du utelate den ytre listog tuplekrever de respektive løsninger.

Svarte 23/08/2018 kl. 17:36
kilden bruker

stemmer
-1

Dette er hvordan du kan transponere en 2x4 tuppel i en 4x2 tuppel.

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

resultat

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Svarte 30/06/2018 kl. 03:23
kilden bruker

stemmer
-1

En annen måte å tenke på unzip, eller transposeer å konvertere en liste med rader i en liste med kolonner.

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')
Svarte 18/04/2018 kl. 02:27
kilden bruker

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