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

时间:2008-08-21 04:29:08

标签: python list matrix transpose

我有一个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])

是否有内置函数可以做到这一点?

14 个答案:

答案 0 :(得分:697)

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(在转换为元组之后),所以不必担心参数的数量太大。

答案 1 :(得分:26)

您也可以

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

在您要求每个元素之前,生成器不会遍历列表,但另一方面,它们会保留对原始列表的引用。

答案 2 :(得分:19)

如果您的列表长度不同,则可能不希望按照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, *[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', )])
[('a', 'b', 'c', 'd', 'e'), (1, 2, 3, 4, None)]

zip()虽然略快。

答案 3 :(得分:15)

我喜欢在我的程序中使用zip(*iterable)(这是您正在寻找的代码段),如下所示:

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

我发现unzip更具可读性。

答案 4 :(得分:12)

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

在问题中给出一个列表元组。

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

打开两个清单。

答案 5 :(得分: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')]

在我看来,解压缩并回到原始版本的更加pythonic方式是:

x,y=zip(*XY)

但是这会返回一个元组,所以如果你需要一个可以使用的列表:

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

答案 6 :(得分:3)

天真的方法

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对应于第j个可迭代的第i个元素,

应用transpose_finite_iterable之后,我们得到

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

a_ij == jn == 2

的这种情况的Python示例
>>> 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) :

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

那么我们该如何处理呢?

... deque

在查看itertools.tee function的文档之后,有一些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 decorator之类的方法来定义用于处理可迭代对象的通用函数,其中一个是有限的,另一个可能是无限的。

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)

在有限的非空可迭代对象上的二元运算符类中,它可以视为自己的逆(数学家称这种函数"involutions")。


作为singledispatch ing的奖励,我们可以处理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中的>>> original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] >>> tuple(map(list, transpose(original))) (['a', 'b', 'c', 'd'], [1, 2, 3, 4]) ,则可以另外使用map built-in function这样的

0.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)] 版本开始向lz package添加了通用解决方案,可以像这样使用

{{1}}

P.S。

目前还没有解决方案(至少很明显)来处理可能无限迭代的可能无限迭代,但是这种情况并不常见。

答案 7 :(得分:1)

由于它返回元组(并且可以使用大量的内存),zip(*zipped)技巧对我来说似乎更有用。

这是一个实际上会给你拉链的函数。

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

答案 8 :(得分:1)

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

答案 9 :(得分:1)

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

答案 10 :(得分:1)

虽然最好使用numpy数组和熊猫,但此函数在被称为zip(*args)时模仿unzip(args)的行为。

允许生成器(例如Python 3中的zip的结果)在循环访问值时作为args传递。

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

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

    :param cls: Container factory. Callable that returns iterable containers,
        with a callable append attribute, to store the unzipped items. Defaults
        to ``list``.
    :type  cls: callable, optional

    :param ocls: Outer container factory. Callable that returns iterable
        containers. with a callable append attribute, to store the inner
        containers (see ``cls``). Defaults to ``tuple``.
    :type  ocls: callable, optional

    :returns: Unzipped items in instances returned from ``cls``, in an instance
        returned from ``ocls``.
    """
    # iter() will return the same iterator passed to it whenever possible.
    items = iter(items)

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

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

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

    return unzipped

要使用列表硬币容器,只需运行unzip(zipped)

unzip(zip(["a","b","c"],[1,2,3])) == (["a","b","c"],[1,2,3])

要使用双端队列或其他append容器,请通过工厂函数。

from collections import deque

unzip([("a",1),("b",2)], deque, list) == [deque(["a","b"]),deque([1,2])]

(装饰cls和/或main_cls来微管理容器的初始化,如上面最后的assert语句中简要显示的那样。)

答案 11 :(得分: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,其中zip返回列表,而不是迭代器。

对于Python 3.x,您需要将结果传递给诸如listtuple之类的函数以耗尽迭代器。对于高效内存的迭代器,您可以省略外部listtuple的相应解决方案。

答案 12 :(得分: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集合。

答案 13 :(得分:-1)

这是将2x4元组转换为4x2元组的方法。

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

结果

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