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

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

有帮助吗?

解决方案

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 直接(转换为元组后),因此无需担心参数数量太大。

其他提示

你也可以这样做

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

在您询问每个元素之前,生成器不会仔细阅读列表,但另一方面,它们确实保留对原始列表的引用。

如果您的列表长度不同,您可能不想按照帕特里克的回答使用 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() 稍微快一些。

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

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

我发现 unzip 更具可读性。

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

解压两个列表。

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

具有这样的数据结构:

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

天真的方法

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-th 可迭代,

并在申请后 transpose_finite_iterable 我们得到

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

此类情况的 Python 示例,其中 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)

但我们不能使用 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 功能, ,有一个 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)

这可以被认为是它自己的逆函数(数学家称这种函数为 “内卷”)在有限非空迭代上的二元运算符类中。


作为奖励 singledispatch我们可以处理 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 返回迭代器,如果有人想要一个 tuplelist就像OP中一样——这可以另外制作 map 内置功能 喜欢

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

广告

我已添加通用解决方案 lz 包裹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)]

附:

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

由于它返回元组(并且可以使用大量内存), 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
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

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

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

之前的答案都没有 有效率的 提供所需的输出,这是 列表元组, ,而不是一个 元组列表. 。对于前者,您可以使用 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,您需要将结果传递给函数,例如 list 或者 tuple 耗尽迭代器。对于内存高效的迭代器,您可以省略外部 listtuple 要求相应的解决方案。

尽管 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 集合可能更有意义。

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

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

结果

[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top