Frage

Ich habe eine Liste von Tupeln mit zwei Elementen und möchte sie in zwei Listen konvertieren, wobei die erste Liste das erste Element in jedem Tupel und die zweite Liste das zweite Element enthält.

Zum Beispiel:

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

Gibt es eine eingebaute Funktion, die das erledigt?

War es hilfreich?

Lösung

zip ist seine eigene Umkehrung!Vorausgesetzt, Sie verwenden den speziellen *-Operator.

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

Dies funktioniert per Anruf zip mit den Argumenten:

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

… außer dass die Argumente an übergeben werden zip direkt (nach der Konvertierung in ein Tupel), sodass Sie sich keine Sorgen machen müssen, dass die Anzahl der Argumente zu groß wird.

Andere Tipps

Das könntest du auch tun

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

Es sollen besser skalieren.Vor allem, wenn Python darauf achtet, die Listenverständnisse nicht zu erweitern, es sei denn, dies ist erforderlich.

(Übrigens erstellt es ein 2-Tupel (Paar) von Listen und nicht wie eine Liste von Tupeln zip tut.)

Wenn Generatoren anstelle tatsächlicher Listen in Ordnung sind, würde dies Folgendes bewirken:

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

Die Generatoren durchforsten die Liste erst, wenn Sie nach den einzelnen Elementen fragen, behalten aber andererseits Verweise auf die Originalliste bei.

Wenn Sie Listen haben, die nicht die gleiche Länge haben, möchten Sie gemäß Patricks Antwort möglicherweise nicht zip verwenden.Das funktioniert:

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

Aber bei Listen unterschiedlicher Länge kürzt zip jedes Element auf die Länge der kürzesten Liste:

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

Sie können eine Karte ohne Funktion verwenden, um leere Ergebnisse mit „Keine“ zu füllen:

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

zip() ist allerdings geringfügig schneller.

Ich benutze es gerne zip(*iterable) (das ist der Code, nach dem Sie suchen) in meinen Programmen wie folgt:

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

ich finde unzip besser lesbar.

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

Gibt ein Tupel von Listen wie in der Frage an.

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

Entpackt die beiden Listen.

Es ist zwar nur eine andere Möglichkeit, aber es hat mir sehr geholfen, also schreibe ich es hier:

Mit dieser Datenstruktur:

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

Ergebend:

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

Der pythonischere Weg, es zu entpacken und zum Original zurückzukehren, ist meiner Meinung nach dieser:

x,y=zip(*XY)

Dies gibt jedoch ein Tupel zurück. Wenn Sie also eine Liste benötigen, können Sie Folgendes verwenden:

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

Naiver Ansatz

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

funktioniert gut für endlich iterierbare (z. B.Sequenzen wie list/tuple/str) von (potenziell unendlichen) Iterablen, die wie folgt dargestellt werden können

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

Wo

  • n in ℕ,
  • a_ij entspricht j-tes Element von i-th iterierbar,

und nach der Bewerbung transpose_finite_iterable wir bekommen

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

Python-Beispiel für einen solchen Fall, wo 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)

Aber wir können es nicht verwenden transpose_finite_iterable noch einmal, um zur ursprünglichen Struktur zurückzukehren iterable Weil result ist eine unendliche Iterable von endlichen Iterablen (tuples in unserem Fall):

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

Wie können wir also mit diesem Fall umgehen?

...und hier kommt das deque

Nachdem wir einen Blick auf die Dokumente von geworfen haben itertools.tee Funktion, es gibt ein Python-Rezept, das mit einigen Modifikationen in unserem Fall hilfreich sein kann

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

Lass uns das Prüfen

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

Synthese

Jetzt können wir eine allgemeine Funktion für die Arbeit mit Iterables von Iterables definieren, von denen einige endlich und andere potenziell unendlich sind functools.singledispatch Dekorateur wie

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)

die als ihre eigene Umkehrung betrachtet werden kann (Mathematiker nennen diese Art von Funktionen). „Involutionen“) in der Klasse binärer Operatoren über endlichen nicht leeren Iterables.


Als Bonus von singledispatching können wir bewältigen numpy Arrays wie

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

und es dann gerne nutzen

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

Notiz

Seit transpose gibt Iteratoren zurück und wenn jemand einen haben möchte tuple von listEs ist wie in OP – dies kann zusätzlich mit gemacht werden map eingebaute Funktion wie

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

Werbung

Ich habe eine allgemeine Lösung hinzugefügt lz Paket aus 0.5.0 Version, die wie verwendet werden kann

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

Es gibt keine (zumindest offensichtliche) Lösung für den Umgang mit potenziell unendlichen Iterables von potenziell unendlichen Iterables, dieser Fall kommt jedoch weniger häufig vor.

Da es Tupel zurückgibt (und jede Menge Speicher verbrauchen kann), ist das zip(*zipped) Trick scheint mir eher clever als nützlich zu sein.

Hier ist eine Funktion, die Ihnen tatsächlich die Umkehrung von zip liefert.

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

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

Erwägen Sie die Verwendung 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]]     

Keine der vorherigen Antworten effizient Bereitstellung der erforderlichen Ausgabe, nämlich a Tupel von Listen, eher als ein Liste der Tupel.Für Ersteres können Sie verwenden tuple mit map.Hier ist der Unterschied:

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

Darüber hinaus gehen die meisten bisherigen Lösungen von Python 2.7 aus zip gibt eine Liste und keinen Iterator zurück.

Für Python 3.x müssen Sie das Ergebnis an eine Funktion wie übergeben list oder tuple um den Iterator zu erschöpfen.Für speichereffiziente Iteratoren können Sie das Äußere weglassen list Und tuple fordert entsprechende Lösungen.

Während zip(*seq) ist sehr nützlich, für sehr lange Sequenzen ist es möglicherweise ungeeignet, da es ein Tupel von Werten erstellt, die übergeben werden müssen.Ich habe zum Beispiel mit einem Koordinatensystem mit über einer Million Einträgen gearbeitet und finde es deutlich schneller, die Sequenzen direkt zu erstellen.

Ein allgemeiner Ansatz wäre etwa so:

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)

Doch je nachdem, was Sie mit dem Ergebnis machen möchten, kann die Wahl der Kollektion einen großen Unterschied machen.In meinem tatsächlichen Anwendungsfall ist die Verwendung von Sets und ohne interne Schleife deutlich schneller als alle anderen Ansätze.

Und wie andere angemerkt haben, kann es sinnvoll sein, stattdessen Numpy- oder Pandas-Sammlungen zu verwenden, wenn Sie dies mit Datensätzen tun.

So können Sie ein 2x4-Tupel in ein 4x2-Tupel transponieren.

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

Ergebnis

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top