Fungsi Transpose / Unzip (kebalikan dari zip)?

505

Saya memiliki daftar 2-item tupel dan saya ingin mengubahnya menjadi 2 daftar di mana yang pertama berisi item pertama di setiap tuple dan daftar kedua berisi item kedua.

Sebagai contoh:

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

Apakah ada fungsi bawaan yang melakukan itu?

Cristian
sumber
6
Jawaban bagus di bawah ini, tetapi juga lihat transpose numpy
opyate
3
Lihat jawaban yang bagus ini untuk melakukan hal yang sama dengan generator alih-alih daftar: how-to-unzip-an-iterator
YvesgereY

Jawaban:

778

zipadalah kebalikannya sendiri! Asalkan Anda menggunakan operator * khusus.

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

Cara kerjanya adalah dengan menelepon zipdengan argumen:

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

... kecuali argumen dilewatkan zipsecara langsung (setelah dikonversi ke tuple), jadi tidak perlu khawatir jumlah argumen menjadi terlalu besar.

Patrick
sumber
20
Oh, kalau saja itu sangat sederhana. Membuka ritsleting zip([], [])dengan cara ini tidak akan membantu Anda [], []. Itu membuat Anda []. Jika saja ...
user2357112 mendukung Monica
4
Ini tidak berfungsi di Python3. Lihat: stackoverflow.com/questions/24590614/…
Tommy
31
@Tommy Ini tidak benar. zipbekerja persis sama di Python 3 kecuali bahwa ia mengembalikan iterator, bukan daftar. Untuk mendapatkan output yang sama seperti di atas, Anda hanya perlu membungkus panggilan zip dalam daftar: list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))akan menampilkan[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
MJeffryes
4
perhatikan: Anda dapat memenuhi masalah memori dan kinerja dengan daftar yang sangat panjang.
Laurent LAPORTE
1
@ JohnP: lists baik-baik saja. Tetapi jika Anda mencoba untuk mewujudkan hasil penuh sekaligus (dengan listifying hasil zip), Anda mungkin menggunakan banyak memori (karena semua yang tuples harus diciptakan sekaligus). Jika Anda hanya bisa mengulangi hasil ziptanpa listifying, Anda akan menghemat banyak memori. Satu-satunya masalah lain adalah jika input memiliki banyak elemen; biayanya adalah itu harus membongkar semuanya sebagai argumen, dan zipakan perlu membuat dan menyimpan iterator untuk semuanya. Ini hanya masalah nyata dengan s yang sangat panjang list(pikirkan ratusan ribu elemen atau lebih).
ShadowRanger
29

Anda juga bisa melakukannya

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

Ini harus lebih baik skala. Terutama jika Python berhasil tidak memperluas pemahaman daftar kecuali diperlukan.

(Kebetulan, itu membuat 2-tupel (pasangan) daftar, bukan daftar tupel, seperti ziphalnya.)

Jika generator bukan daftar yang sebenarnya ok, ini akan melakukan itu:

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

Generator tidak menelusuri daftar sampai Anda meminta setiap elemen, tetapi di sisi lain, mereka tetap referensi ke daftar asli.

Anders Eurenius
sumber
8
"Terutama jika Python berhasil tidak memperluas pemahaman daftar kecuali diperlukan." mmm ... secara normal, daftar pemahaman diperluas segera - atau apakah saya mendapatkan kesalahan?
glglgl
1
@glglgl: Tidak, Anda mungkin benar. Saya hanya berharap beberapa versi masa depan mungkin mulai melakukan hal yang benar. (Bukan tidak mungkin untuk mengubah, efek samping semantik yang membutuhkan perubahan mungkin sudah tidak disarankan.)
Anders Eurenius
9
Yang Anda harapkan adalah generator expresion - yang sudah ada.
glglgl
12
Ini tidak 'skala lebih baik' dari zip(*x)versi. zip(*x)hanya membutuhkan satu kali melewati loop, dan tidak menggunakan elemen tumpukan.
habnabit
1
Apakah itu "berskala lebih baik" atau tidak tergantung dari siklus hidup data asli dibandingkan dengan data yang ditransfusikan. Jawaban ini hanya lebih baik daripada menggunakan zipjika use-case adalah bahwa data yang ditransposisikan digunakan dan dibuang segera, sementara daftar aslinya tetap dalam memori lebih lama.
Ekevoo
21

Jika Anda memiliki daftar yang panjangnya tidak sama, Anda mungkin tidak ingin menggunakan zip sesuai jawaban Patricks. Ini bekerja:

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

Tetapi dengan daftar panjang yang berbeda, zip memotong setiap item dengan panjang daftar terpendek:

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

Anda dapat menggunakan peta tanpa fungsi untuk mengisi hasil kosong dengan Tidak Ada:

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

zip () sedikit lebih cepat.

Chris
sumber
4
Anda juga bisa menggunakanizip_longest
Marcin
3
Dikenal sebagai zip_longestuntuk pengguna python3.
zezollo
1
@GrijeshChauhan Saya tahu ini benar-benar tua, tetapi ini adalah fitur bawaan yang aneh: docs.python.org/2/library/functions.html#map "Jika fungsi Tidak ada, fungsi identitas diasumsikan; jika ada beberapa argumen, map () mengembalikan daftar yang terdiri dari tupel berisi item yang sesuai dari semua iterables (semacam operasi transpos). Argumen iterable dapat berupa urutan atau objek yang dapat diulang; hasilnya selalu berupa daftar. "
cactus1
18

Saya suka menggunakan zip(*iterable)(yang merupakan bagian dari kode yang Anda cari) dalam program saya sebagai berikut:

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

Saya menemukan unziplebih mudah dibaca.

wassimans
sumber
12
>>> 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])

Memberikan beberapa daftar seperti pada pertanyaan.

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

Buka paket kedua daftar.

Noyer282
sumber
8

Pendekatan naif

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

berfungsi dengan baik untuk iterable terbatas (misalnya urutan seperti list/ tuple/ str) dari iterables (berpotensi tak terbatas) yang dapat diilustrasikan seperti

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

dimana

  • n in ℕ,
  • a_ijsesuai dengan jelemen i-th dari iterable,

dan setelah mendaftar transpose_finite_iterablekita dapatkan

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

Contoh python dari kasus seperti itu di mana 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)

Tetapi kita tidak dapat menggunakan transpose_finite_iterablelagi untuk kembali ke struktur asli iterablekarena resultiterable iterable terbatas hingga terbatas ( tupledalam kasus kami):

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

Jadi bagaimana kita bisa menangani kasus ini?

... dan ini dia deque

Setelah kita melihat pada docs of itertools.teefunction , ada resep Python yang dengan beberapa modifikasi dapat membantu dalam kasus kita

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

mari kita periksa

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

Perpaduan

Sekarang kita dapat mendefinisikan fungsi umum untuk bekerja dengan iterables dari iterables yang terbatas dan yang lainnya berpotensi tak terbatas menggunakan functools.singledispatchdekorator seperti

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)

yang dapat dianggap sebagai kebalikannya sendiri (ahli matematika menyebut fungsi semacam ini "involusi" ) di kelas operator biner melalui iterables terbatas yang tidak kosong.


Sebagai bonus singledispatchkami dapat menangani numpyarray seperti

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

dan kemudian gunakan seperti

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

Catatan

Sejak transposekembali iterator dan jika seseorang ingin memiliki tupledari lists seperti di OP - ini dapat dibuat juga dengan mapbuilt-in fungsi seperti

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

Iklan

Saya telah menambahkan solusi umum ke lzpaket dari 0.5.0versi yang dapat digunakan seperti

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

Tidak ada solusi (setidaknya jelas) untuk menangani iterable yang berpotensi tak terbatas dari iterables yang berpotensi tak terbatas, tetapi kasus ini lebih jarang terjadi.

Azat Ibrakov
sumber
4

Ini hanya cara lain untuk melakukannya tetapi itu sangat membantu saya, jadi saya menulisnya di sini:

Memiliki struktur data ini:

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

Yang menghasilkan:

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

Cara yang lebih pythonic untuk unzip dan kembali ke aslinya adalah yang ini menurut saya:

x,y=zip(*XY)

Tapi ini mengembalikan tuple jadi jika Anda memerlukan daftar, Anda dapat menggunakan:

x,y=(list(x),list(y))
GM
sumber
3

Pertimbangkan untuk menggunakan 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]]     
Neil G
sumber
1

Karena mengembalikan tupel (dan dapat menggunakan banyak memori), zip(*zipped)triknya tampaknya lebih pintar daripada berguna, bagi saya.

Inilah fungsi yang benar-benar akan memberi Anda kebalikan dari 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
Waylon Flinn
sumber
Membuat ulang tupel yang terus-menerus tampaknya tidak efisien bagi saya, tetapi Anda dapat memperluas pendekatan ini menggunakan deques yang dapat mengalokasikan kembali memori.
Charlie Clark
0

Tidak satu pun dari jawaban sebelumnya yang secara efisien memberikan output yang diperlukan, yang merupakan daftar tuple , bukan daftar tuple . Untuk yang pertama, Anda bisa menggunakannya tuplebersama map. Inilah perbedaannya:

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

Selain itu, sebagian besar solusi sebelumnya mengasumsikan Python 2.7, di mana zipmengembalikan daftar daripada iterator.

Untuk Python 3.x, Anda harus meneruskan hasilnya ke fungsi seperti listatau tupleuntuk menghabiskan iterator. Untuk iterator yang efisien-memori, Anda dapat menghilangkan bagian luar listdan tuplememinta solusi masing-masing.

jpp
sumber
0

Meskipun zip(*seq)sangat berguna, mungkin tidak cocok untuk urutan yang sangat lama karena akan membuat tuple nilai yang akan diteruskan. Misalnya, saya telah bekerja dengan sistem koordinat dengan lebih dari satu juta entri dan merasa lebih cepat untuk membuat urutan langsung.

Pendekatan generik akan seperti ini:

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)

Tetapi, tergantung pada apa yang ingin Anda lakukan dengan hasilnya, pilihan koleksi dapat membuat perbedaan besar. Dalam kasus penggunaan saya yang sebenarnya, menggunakan set dan tidak ada loop internal, terasa lebih cepat daripada semua pendekatan lainnya.

Dan, seperti yang telah dicatat orang lain, jika Anda melakukan ini dengan dataset, mungkin masuk akal untuk menggunakan koleksi Numpy atau Pandas sebagai gantinya.

Charlie Clark
sumber
0

Sementara numpy arrays dan panda mungkin lebih disukai, fungsi ini meniru perilaku zip(*args)ketika dipanggil unzip(args).

Memungkinkan generator untuk diteruskan karena argsiterates melalui nilai-nilai. Hiasi clsdan / atau main_clsuntuk inisialisasi pengelolaan mikro.

def unzip(items, cls=list, main_cls=tuple):
    """Zip function in reverse.

    :param items: Zipped-like iterable.
    :type  items: iterable

    :param cls: Callable that returns iterable with callable append attribute.
        Defaults to `list`.
    :type  cls: callable, optional

    :param main_cls: Callable that returns iterable with callable append
        attribute. Defaults to `tuple`.
    :type  main_cls: callable, optional

    :returns: Unzipped items in instances returned from `cls`, in an instance
        returned from `main_cls`.

    :Example:

        assert unzip(zip(["a","b","c"],[1,2,3])) == (["a","b",c"],[1,2,3])
        assert unzip([("a",1),("b",2),("c",3)]) == (["a","b","c"],[1,2,3])
        assert unzip([("a",1)], deque, list) == [deque(["a"]),deque([1])]
        assert unzip((["a"],["b"]), lambda i: deque(i,1)) == (deque(["b"]),)
    """
    items = iter(items)

    try:
        i = next(items)
    except StopIteration:
        return main_cls()

    unzipped = main_cls(cls([v]) for v in i)

    for i in items:
        for c,v in zip(unzipped,i):
            c.append(v)

    return unzipped
Trasp
sumber