Chức năng Transpose / Unzip (nghịch đảo của zip)?


505

Tôi có một danh sách các bộ dữ liệu 2 mục và tôi muốn chuyển đổi chúng thành 2 danh sách trong đó danh sách đầu tiên chứa mục đầu tiên trong mỗi bộ và danh sách thứ hai giữ mục thứ hai.

Ví dụ:

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

Có một hàm dựng sẵn nào đó không?


6
Những câu trả lời tuyệt vời dưới đây, nhưng cũng hãy nhìn vào sự hoán vị của
Numpy

3
Xem câu trả lời hay này để làm tương tự với các trình tạo thay vì danh sách: how-to-unzip-an-iterator
YvesgereY

Câu trả lời:


778

ziplà nghịch đảo của chính nó! Miễn là bạn sử dụng toán tử * đặc biệt.

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

Cách thức hoạt động này là bằng cách gọi zipvới các đối số:

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

Không ngoại trừ các đối số được truyền ziptrực tiếp (sau khi được chuyển đổi thành một tuple), vì vậy không cần phải lo lắng về số lượng đối số trở nên quá lớn.


20
Ồ, nếu chỉ có nó rất đơn giản. Giải nén zip([], [])theo cách này không giúp bạn [], []. Nó có được bạn []. Nếu chỉ ...
user2357112 hỗ trợ Monica

4
Điều này không hoạt động trong Python3. Xem: stackoverflow.com/questions/24590614/ Mạnh
Tommy

31
@ Mẹ ơi Điều này không đúng. ziphoạt động chính xác như nhau trong Python 3 ngoại trừ việc nó trả về một trình vòng lặp thay vì danh sách. Để có được đầu ra giống như trên, bạn chỉ cần bọc cuộc gọi zip trong danh sách: list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))sẽ xuất[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
MJeffryes 11/03/2015

4
chú ý: bạn có thể gặp các vấn đề về bộ nhớ và hiệu năng với các danh sách rất dài.
Laurent LAPORTE

1
@JohnP: lists ổn. Nhưng nếu bạn cố gắng nhận ra toàn bộ kết quả cùng một lúc (bằng cách listlấy kết quả của zip), bạn có thể sử dụng rất nhiều bộ nhớ (vì tất cả các tuples phải được tạo cùng một lúc). Nếu bạn chỉ có thể lặp đi lặp lại kết quả zipmà không có ý định list, bạn sẽ tiết kiệm được rất nhiều bộ nhớ. Mối quan tâm duy nhất khác là nếu đầu vào có nhiều yếu tố; cái giá phải trả là nó phải giải nén tất cả chúng dưới dạng đối số và zipsẽ cần tạo và lưu trữ các trình vòng lặp cho tất cả chúng. Đây chỉ là một vấn đề thực sự với s rất dài list(nghĩ hàng trăm ngàn yếu tố trở lên).
ShadowRanger

29

Bạn cũng có thể làm

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

nên quy mô tốt hơn. Đặc biệt là nếu Python làm tốt việc không mở rộng việc hiểu danh sách trừ khi cần thiết.

(Ngẫu nhiên, nó tạo ra một danh sách gồm 2 tuple, thay vì một danh sách các bộ dữ liệu, giống như zipvậy.)

Nếu máy phát điện thay vì danh sách thực tế là ok, điều này sẽ làm điều đó:

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

Các trình tạo không munch thông qua danh sách cho đến khi bạn yêu cầu từng phần tử, nhưng mặt khác, chúng vẫn giữ các tham chiếu đến danh sách ban đầu.


8
"Đặc biệt là nếu Python làm tốt việc không mở rộng việc hiểu danh sách trừ khi cần thiết." mmm ... thông thường, việc hiểu danh sách được mở rộng ngay lập tức - hoặc tôi có bị lỗi gì không?
glglgl

1
@glglgl: Không, có lẽ bạn đúng. Tôi chỉ hy vọng một số phiên bản trong tương lai có thể bắt đầu làm điều đúng đắn. (Không thể thay đổi, ngữ nghĩa tác dụng phụ cần thay đổi có lẽ đã không được khuyến khích.)
Anders Eurenius

9
Những gì bạn hy vọng nhận được là một expresion máy phát điện - đã tồn tại.
glglgl

12
Điều này không 'quy mô tốt hơn' so với zip(*x)phiên bản. zip(*x)chỉ yêu cầu một lần đi qua vòng lặp và không sử dụng hết các phần tử stack.
thói quen

1
Việc nó "có tỷ lệ tốt hơn" hay không phụ thuộc vào vòng đời của dữ liệu gốc so với dữ liệu được chuyển đổi. Câu trả lời này chỉ tốt hơn so với việc sử dụng zipnếu trường hợp sử dụng là dữ liệu được chuyển đổi được sử dụng và loại bỏ ngay lập tức, trong khi các danh sách ban đầu ở lại trong bộ nhớ lâu hơn nhiều.
Ekevoo

21

Nếu bạn có các danh sách không có cùng độ dài, bạn có thể không muốn sử dụng zip theo câu trả lời của Patricks. Những công việc này:

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

Nhưng với các danh sách độ dài khác nhau, zip cắt ngắn từng mục theo độ dài của danh sách ngắn nhất:

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

Bạn có thể sử dụng bản đồ không có chức năng để điền vào kết quả trống với Không:

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

zip () là nhanh hơn một chút mặc dù.


4
Bạn cũng có thể sử dụngizip_longest
Marcin

3
Được biết đến như zip_longestđối với người dùng python3.
zezoche

1
@GrijeshChauhan Tôi biết điều này thực sự là cũ, nhưng đó là một xây dựng trong tính năng kỳ lạ: docs.python.org/2/library/functions.html#map "Nếu chức năng là Không, chức năng nhận diện được giả định, nếu có nhiều tranh cãi, map () trả về một danh sách bao gồm các bộ chứa các mục tương ứng từ tất cả các lần lặp (một loại hoạt động chuyển vị). Các đối số lặp có thể là một chuỗi hoặc bất kỳ đối tượng lặp lại nào, kết quả luôn luôn là một danh sách. "
xương rồng1

18

Tôi thích sử dụng zip(*iterable)(đó là đoạn mã bạn đang tìm kiếm) trong các chương trình của mình như vậy:

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

Tôi thấy unzipdễ đọc hơn.


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

Cung cấp một bộ danh sách như trong câu hỏi.

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

Giải nén hai danh sách.


8

Cách tiếp cận ngây thơ

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

hoạt động tốt đối với các lần lặp hữu hạn (ví dụ như các chuỗi như list/ tuple/ str) của các lần lặp (có khả năng vô hạn) có thể được minh họa như

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

Ở đâu

  • n in ℕ,
  • a_ijtương ứng với jphần tử i-th của -th iterable,

và sau khi áp dụng transpose_finite_iterablechúng tôi nhận được

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

Ví dụ Python về trường hợp như vậy trong đó 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)

Nhưng chúng ta không thể sử dụng transpose_finite_iterablelại để trở về cấu trúc của bản gốc iterableresultlà một số lần lặp vô hạn của các số lần lặp hữu hạn ( tupletrong trường hợp của chúng ta):

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

Vậy làm thế nào chúng ta có thể đối phó với trường hợp này?

... và đây là deque

Sau khi chúng ta xem tài liệu về itertools.teehàm , có một công thức Python mà với một số sửa đổi có thể giúp ích trong trường hợp của chúng ta

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

hãy kiểm tra

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

Tổng hợp

Bây giờ chúng ta có thể định nghĩa hàm chung để làm việc với các iterables của các iterables là hữu hạn và các hàm khác là vô hạn khi sử dụng functools.singledispatchtrang trí như

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)

có thể được coi là nghịch đảo của chính nó (các nhà toán học gọi loại hàm này là "sự tham gia" ) trong lớp các toán tử nhị phân trên các phép lặp không hữu hạn.


Như một phần thưởng của singledispatching, chúng ta có thể xử lý numpycác mảng như

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

và sau đó sử dụng nó như

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

Ghi chú

transposetrả về các trình vòng lặp và nếu ai đó muốn có một tupleđiểm listgiống như trong OP - điều này có thể được thực hiện bổ sung với mapchức năng tích hợp sẵn như

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

Quảng cáo

Tôi đã thêm giải pháp tổng quát vào lzgói từ 0.5.0phiên bản có thể được sử dụng như

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

Không có giải pháp (ít nhất là rõ ràng) để xử lý khả năng lặp vô hạn của các lần lặp có khả năng vô hạn, nhưng trường hợp này ít phổ biến hơn.


4

Đó chỉ là một cách khác để làm điều đó nhưng nó đã giúp tôi rất nhiều vì vậy tôi viết nó ở đây:

Có cấu trúc dữ liệu này:

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

Kết quả là:

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

Theo tôi thì cách pythonic hơn để giải nén nó và quay lại bản gốc là theo cách này:

x,y=zip(*XY)

Nhưng điều này trả về một tuple vì vậy nếu bạn cần một danh sách, bạn có thể sử dụng:

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

3

Cân nhắc sử dụng 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]]     

1

Vì nó trả về các bộ dữ liệu (và có thể sử dụng hàng tấn bộ nhớ), nên với tôi, zip(*zipped)mẹo này có vẻ thông minh hơn là hữu ích.

Đây là một chức năng thực sự sẽ cung cấp cho bạn nghịch đảo của 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

Việc liên tục tái tạo các bộ dữ liệu có vẻ không hiệu quả đối với tôi nhưng bạn có thể mở rộng cách tiếp cận này bằng cách sử dụng các công cụ có thể phân bổ bộ nhớ.
Charlie Clark

0

Không có câu trả lời nào trước đây cung cấp hiệu quả đầu ra cần thiết, đó là một bộ danh sách , thay vì danh sách các bộ dữ liệu . Đối với trước đây, bạn có thể sử dụng tuplevới map. Đây là sự khác biệt:

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

Ngoài ra, hầu hết các giải pháp trước đây đều giả sử Python 2.7, trong đó ziptrả về một danh sách thay vì một trình vòng lặp.

Đối với Python 3.x, bạn sẽ cần chuyển kết quả cho một hàm như listhoặc tuplelàm cạn kiệt trình lặp. Đối với các trình vòng lặp hiệu quả bộ nhớ, bạn có thể bỏ qua bên ngoài listtuplegọi các giải pháp tương ứng.


0

Mặc dù zip(*seq)rất hữu ích, nhưng nó có thể không phù hợp với các chuỗi rất dài vì nó sẽ tạo ra một bộ giá trị được truyền vào. Ví dụ, tôi đã làm việc với một hệ tọa độ với hơn một triệu mục và thấy nó nhanh hơn đáng kể để tạo các trình tự trực tiếp.

Một cách tiếp cận chung chung sẽ là một cái gì đó như thế này:

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)

Nhưng, tùy thuộc vào những gì bạn muốn làm với kết quả, sự lựa chọn của bộ sưu tập có thể tạo ra một sự khác biệt lớn. Trong trường hợp sử dụng thực tế của tôi, sử dụng các bộ và không có vòng lặp bên trong, nhanh hơn đáng kể so với tất cả các phương pháp khác.

Và, như những người khác đã lưu ý, nếu bạn đang làm điều này với các bộ dữ liệu, có thể có ý nghĩa khi sử dụng các bộ sưu tập Numpy hoặc Pandas thay thế.


0

Trong khi mảng numpy và gấu trúc có thể được ưa thích hơn, chức năng này bắt chước hành vi của zip(*args)khi được gọi là unzip(args).

Cho phép các máy phát được truyền argskhi nó lặp qua các giá trị. Trang trí clsvà / hoặc main_clsđể vi quản lý khởi tạo container.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.