我有一个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])
有没有内置函数可以做到这一点?
我有一个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])
有没有内置函数可以做到这一点?
当前回答
在2。X, 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(在被转换为元组之后),所以没有必要担心参数的数量变得太大。
在3。X, zip返回一个惰性迭代器,但这是简单的转换:
>>> list(zip(*[('a', 1), ('b', 2), ('c', 3), ('d', 4)]))
[('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
其他回答
虽然numpy数组和pandas可能更可取,但此函数在作为unzip(args)调用时模仿zip(*args)的行为。
允许生成器(如Python 3中zip的结果)在遍历值时作为参数传递。
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(zip),如
unzip(zip(["a","b","c"],[1,2,3])) == (["a","b","c"],[1,2,3])
要使用deques或其他带有附加的容器,需要传递一个工厂函数。
from collections import deque
unzip([("a",1),("b",2)], deque, list) == [deque(["a","b"]),deque([1,2])]
(装饰cls和/或main_cls以微管理容器初始化,如上面最后的assert语句所简单显示的那样。)
天真的方法
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是有限可迭代对象(在我们的例子中是元组)的无限可迭代对象:
>>> 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 recipe,经过一些修改可以帮助我们的情况
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定义用于处理可迭代对象的可迭代对象的通用函数,其中一个是有限的,另一个可能是无限的。单分派装饰器
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)
在有限非空可迭代对象上的二元运算符类中,它可以被认为是它自己的逆(数学家称这种函数为“对合”)。
作为单分派的额外好处,我们可以像这样处理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]])
Note
由于转置返回迭代器,如果有人想要一个像OP中那样由列表组成的元组——这可以用map内置函数如
>>> 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包中添加了通用解决方案,可以像这样使用
>>> 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)]
P.S.
没有解决方案(至少是明显的)来处理潜在无限迭代对象的潜在无限迭代对象,但这种情况不太常见。
你也可以
result = ([ a for a,b in original ], [ b for a,b in original ])
它的可伸缩性应该更好。特别是如果Python不扩展列表推导式,除非需要的话。
(顺便说一句,它生成一个二元组(对)列表,而不是像zip那样生成一个元组列表。)
如果生成器代替实际的列表是可以的,这将做到:
result = (( a for a,b in original ), ( b for a,b in original ))
在您请求每个元素之前,生成器不会仔细检查列表,但另一方面,它们会保留对原始列表的引用。
考虑使用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]]
我喜欢在我的程序中使用zip(*iterable)(这是你正在寻找的代码段),如下所示:
def unzip(iterable):
return zip(*iterable)
我发现unzip更具可读性。