关于python:Transpose / Unzip功能(zip的反转)?

Transpose/Unzip Function (inverse of zip)?

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

例如:

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

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


zip是它自己的反义词!如果您使用特殊*运算符。

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

其工作方式是通过以下参数调用zip

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

…除了参数直接传递给zip(转换成元组后),所以不必担心参数的数量变得太大。


你也可以

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

它应该有更好的伸缩性。尤其是如果python在不扩展列表理解方面做得很好,除非需要。

(顺便说一下,它是一对列表,而不是像zip那样的元组列表。)

如果生成器(而不是实际列表)正常,则可以这样做:

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

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


如果您的列表长度不相同,您可能不想按照帕特里克的回答使用zip。这项工作:

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

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

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

可以使用不带函数的map将空结果填充为无:

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

不过,zip()速度稍快。


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

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

我发现unzip更易读。


1
2
3
>>> 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])

给出问题中的一组列表。

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

打开两个列表。


这只是另一种方法,但它对我有很大帮助,所以我在这里写了:

具有这种数据结构:

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

导致:

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

在我看来,解压并回到原版的更为Python式的方法是:

1
x,y=zip(*XY)

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

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


天真的方法

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

对于有限不可迭代(如list/tuple/str等序列)的(潜在无限)iterables,可以很好地工作,如

1
2
3
4
5
| |a_00| |a_10| ... |a_n0| |
| |a_01| |a_11| ... |a_n1| |
| |... | |... | ... |... | |
| |a_0i| |a_1i| ... |a_ni| |
| |... | |... | ... |... | |

哪里

  • n in ?
  • a_ij对应于ith iterable的jth元素,

应用transpose_finite_iterable后,我们得到

1
2
3
4
| |a_00| |a_01| ... |a_0i| ... |
| |a_10| |a_11| ... |a_1i| ... |
| |... | |... | ... |... | ... |
| |a_n0| |a_n1| ... |a_ni| ... |

python示例,其中a_ij == jn == 2

1
2
3
4
5
6
7
>>> 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是有限iterable s的无穷大iterables(在我们的例子中是tuples):

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

那么我们如何处理这个案子呢?

…江户一号〔14〕来了。

在我们看了itertools.tee函数的文档之后,有一个python配方,通过一些修改可以帮助我们解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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))

让我们检查一下

1
2
3
4
5
6
7
8
9
>>> 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

。合成

现在我们可以定义一般的函数来处理iterables的iterables,其中一个是有限的,另一个可能是无限的,使用functools.singledispatch类装饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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)

在有限非空iterables上的二元算子类中,它可以被看作是它自己的逆(数学家称这种函数为"对合")。

作为singledispatch的额外功能,我们可以处理numpy数组,例如

1
2
3
import numpy as np
...
transpose.register(np.ndarray, np.transpose)

然后像这样使用

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

注释

由于transpose返回迭代器,并且如果有人想在op中使用listtuple,那么可以使用map内置函数,例如

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

。广告

我在0.5.0版本的lz包中添加了通用的解决方案,可以像

1
2
3
>>> 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)]

号附笔。

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


1
2
3
4
5
6
7
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])


考虑使用更多的itertools.unzip:

1
2
3
4
>>> 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]]


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

这是一个函数,它实际上会给你zip的倒数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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


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

一般方法如下:

1
2
3
4
5
6
7
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集合可能是有意义的。


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

1
2
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对各自解决方案的调用。


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

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

结果

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