我有一个 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(*x)
版本“扩展得更好”。 zip(*x)
只需要一次循环,并且不会用完堆栈元素。
zip
更好。
我喜欢在我的程序中使用 zip(*iterable)
(这是您要查找的代码),如下所示:
def unzip(iterable):
return zip(*iterable)
我发现 unzip
更具可读性。
如果您的列表长度不同,您可能不想按照帕特里克的回答使用 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() 稍微快一点。
izip_longest
zip_longest
。
>>> 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)]
解压缩这两个列表。
天真的方法
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,
a_ij 对应于第 i 个可迭代的第 j 个元素,
应用 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
那么我们该如何处理这种情况呢?
...双端队列来了
在我们查看 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
的奖励,我们可以处理 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 中拥有 list
的 tuple
- 这可以另外使用 map
built-in function 来完成
>>> 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
package 添加了通用解决方案,可以像这样使用
>>> 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)]
附言
没有解决方案(至少显而易见)来处理潜在无限迭代的潜在无限迭代,但这种情况不太常见。
这只是另一种方法,但它对我帮助很大,所以我在这里写:
有这个数据结构:
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))
考虑使用 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]]
前面的答案都有效地提供了所需的输出,即列表元组,而不是元组列表。对于前者,您可以将 tuple
与 map
一起使用。区别如下:
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
等函数以耗尽迭代器。对于内存高效的迭代器,您可以省略各自解决方案的外部 list
和 tuple
调用。
虽然 numpy 数组和 pandas 可能更可取,但此函数在调用 unzip(args)
时模仿 zip(*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
以对容器初始化进行微管理,如上面最后的断言语句所示。)
因为它返回元组(并且可以使用大量内存),所以对我来说,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
虽然 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 集合可能是有意义的。
这是一个简单的单行答案,可以产生所需的输出:
original = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
list(zip(*original))
# [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
不定期副业成功案例分享
zip([], [])
不会得到[], []
。它让你[]
。要是...zip
在 Python 3 中的工作方式完全相同,只是它返回的是迭代器而不是列表。为了获得与上面相同的输出,您只需将 zip 调用包装在一个列表中:list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))
将输出[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
list
很好。但是,如果您尝试一次性实现全部结果(通过list
对zip
的结果进行化),您可能会使用大量内存(因为 alltuple
必须是一次创建)。如果您可以只迭代zip
的结果而无需list
化,您将节省大量内存。唯一的另一个问题是输入是否有很多元素;代价是它必须将它们全部解压缩为参数,并且zip
将需要为所有它们创建和存储迭代器。这只是 非常 长的list
(想想数十万或更多元素)的真正问题。