转置/解压缩功能(zip的反函数)?


504

我有一个2项元组的列表,我想将它们转换为2个列表,其中第一个包含每个元组中的第一项,第二个包含第二项。

例如:

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

有内置的功能吗?


6
以下是很好的答案,但同时也请看一下numpy的转置
-opyate

3
看到这个不错的答案,用发电机代替清单做同样的事情:how-to-unzip-an-iterator
YvesgereY 2016年

Answers:


777

zip是它自己的逆!前提是您使用特殊的*运算符。

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

它的工作方式是通过调用zip参数:

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

…除了参数zip直接传递(在转换为元组之后)之外,因此不必担心参数数量太大。


20
哦,只要这么简单。用zip([], [])这种方式解压缩不会使您感到困惑[], []。它让你[]。如果仅...
user2357112支持Monica

4
这在Python3中不起作用。请参阅:stackoverflow.com/questions/24590614/...
汤米

30
@Tommy这是不正确的。zip除了返回迭代器而不是列表之外,在Python 3中的工作原理完全相同。为了获得与上述相同的输出,您只需要将zip调用包装在一个列表中即可: list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))将输出[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
MJeffryes

4
注意:清单很长,您可能会遇到内存和性能问题。
洛朗·拉波特

1
@JohnP:list很好。但是,如果你试图(通过实现完整的结果一下子listifying的结果zip),你可能会使用大量内存(因为所有tuple小号必须立即创建)。如果你可以遍历的结果zip,而不listifying,你可以节省大量的内存。唯一需要关注的是输入中是否包含许多元素。这样做的代价是必须将它们全部解压缩为参数,并且zip需要为所有它们创建和存储迭代器。这是一个非常长的lists 真正的问题(请考虑成千上万个元素或更多)。
ShadowRanger

29

你也可以

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

应该更好地扩展。特别是如果Python除非需要,否则最好不要扩展列表推导。

(顺便说一句,它会组成一个2元组(一对)的列表,而不是一个元组列表,例如 zip。)

如果可以使用生成器而不是实际列表,则可以这样做:

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

生成器在您请求每个元素之前不会仔细检查列表,但是另一方面,它们会保留对原始列表的引用。


8
“特别是如果Python除非需要,否则不要擅长扩展列表推导。” 嗯...通常,列表理解会立即扩展-还是我出了错?
glglgl 2011年

1
@glglgl:不,您可能是对的。我只是希望将来的某些版本可以开始做正确的事。(更改并非并非不可能,需要更改的副作用语义已经被劝阻了。)
Anders Eurenius 2012年

9
您希望获得的是发电机表达式-已经存在。
glglgl 2012年

12
这并不比zip(*x)版本“更好地扩展” 。zip(*x)只需要通过循环一次,并且不会用完堆栈元素。
habnabit 2013年

1
它是否“更好地缩放”取决于原始数据与转置数据相比的生命周期。zip如果用例是转置后的数据被立即使用并丢弃,而原始列表在内存中保留的时间更长,则此答案仅比使用更好。
Ekevoo 2015年

21

如果列表的长度不同,则可能不希望按照Patricks的答案使用zip。这有效:

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

但是使用不同的长度列表,zip会将每个项目截断为最短列表的长度:

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

您可以使用不带功能的map来用None填充空白结果:

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

zip()稍快一些。


4
您还可以使用izip_longest
Marcin 2013年

3
zip_longestpython3用户而闻名。
zezollo

1
@GrijeshChauhan我知道这确实很老,但是它是内置的怪异功能:docs.python.org/2/library/functions.html#map “如果function为None,则假定为identity函数;如果有多个参数, map()返回一个包含元组的列表,该元组包含所有可迭代对象中的对应项(一种转置操作)。可迭代参数可以是序列或任何可迭代对象;结果始终是列表。”
cactus1

18

我喜欢在程序中使用zip(*iterable)(这是您要查找的代码):

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

我发现unzip更具可读性。


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

给出问题中的列表元组。

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

解压缩两个列表。


8

天真的方法

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

对于(潜在无限)可迭代的有限可迭代(例如list/ tuple/的序列str),效果很好

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

哪里

  • n in ℕ
  • a_ij对应于-th可迭代的j-th元素i

申请后transpose_finite_iterable我们得到

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

这种情况的Python示例,其中a_ij == jn == 2

>>> from itertools import count
>>> iterable = [count(), count()]
>>> result = transpose_finite_iterable(iterable)
>>> next(result)
(0, 0)
>>> next(result)
(1, 1)

但是我们不能transpose_finite_iterable再次使用它来返回原始的结构,iterable因为它result是有限迭代的无限迭代(tuple在我们的例子中是s):

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

那么我们该如何处理呢?

...这是 deque

看完itertools.teefunction文档后,有一些Python配方可以通过一些修改来帮助解决我们的问题

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

让我们检查

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

合成

现在我们可以定义通用函数来处理可迭代的可迭代对象,其中一些是有限的,而另一个则可以使用functools.singledispatch装饰器(例如)

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)

在有限非空可迭代项上的二元运算符类中,可以将其视为自身的逆(数学家称这种函数为“对合”)。


作为singledispatching 的奖励,我们可以处理numpy类似

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

然后像

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

注意

由于transpose返回迭代器,并且如果有人希望在OP中具有的tuplelist则可以通过map内置函数(例如

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

广告

我已经添加推广解决方案lz0.5.0版本,可以像使用

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

聚苯乙烯

没有用于处理潜在无限迭代的潜在无限迭代的解决方案(至少很明显),但是这种情况并不常见。


4

这只是另一种实现方式,但是它对我有很大帮助,所以我在这里写下来:

具有以下数据结构:

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

导致:

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

我认为,将其解压缩并返回原始格式的更Python方式是:

x,y=zip(*XY)

但这返回一个元组,因此如果您需要一个列表,则可以使用:

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

3

考虑使用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

因为它返回元组(并且可以使用大量内存),所以zip(*zipped)对我来说,这个技巧似乎比有用的还要聪明。

这是一个实际上将为您提供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

连续重新创建元组对我来说似乎效率不高,但是您可以使用可以预先分配内存的双端队列扩展此方法。
查理·克拉克

0

先前的答案都没有有效地提供所需的输出,即列表的元组,而不是元组的列表。对于前者,你可以使用与。区别在于:tuplemap

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

此外,大多数以前的解决方案都假定使用Python 2.7,在Python 2.7中zip返回列表而不是迭代器。

对于Python 3.x,您需要将结果传递给诸如listtuple耗尽迭代器的函数。对于内存高效的迭代器,您可以省略外部list和外部tuple调用各自的解决方案。


0

虽然zip(*seq)非常有用,但可能不适用于很长的序列,因为它将创建要传递的值的元组。例如,我一直在使用具有超过一百万个条目的坐标系,并且发现创建它的速度明显更快序列直接。

通用方法如下所示:

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)

但是,根据您要对结果执行的操作,收集的选择可能会产生很大的不同。在我的实际用例中,使用集而不使用内部循环比所有其他方法明显更快。

而且,正如其他人指出的那样,如果您要对数据集执行此操作,则可以改用Numpy或Pandas集合。


0

虽然numpy数组和熊猫可能是更可取的,但此函数模仿zip(*args)as时的行为unzip(args)

允许在args迭代值时传递生成器。装饰cls和/或main_cls微管理容器初始化。

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.