迭代一对不同长度的可迭代对象的最简洁方法,包装较短的可迭代对象?
Cleanest way to iterate over pair of iterables of different lengths, wrapping the shorter iterable?
如果我有两个不同长度的可迭代对象,我怎样才能最干净地将它们配对,重新使用较短的值,直到消耗掉较长的所有值?
例如,给定两个列表
l1 = ['a', 'b', 'c']
l2 = ['x', 'y']
最好有一个函数 fn()
生成对:
>>> fn(l1, l2)
[('a', 'x'), ('b', 'y'), ('c', 'x')]
我发现我可以编写一个函数来执行此操作
def fn(l1, l2):
if len(l1) > len(l2):
return [(v, l2[i % len(l2)]) for i, v in enumerate(l1)]
return [(l1[i % len(l1)], v) for i, v in enumerate(l2)]
>>> fn(l1, l2)
[('a', 'x'), ('b', 'y'), ('c', 'x')]
>>> l2 = ['x', 'y', 'z', 'w']
>>> fn(l1,l2)
[('a', 'x'), ('b', 'y'), ('c', 'z'), ('a', 'w')]
但是,我很贪心,很好奇还有什么其他方法存在?所以我可能select最明显和优雅的,并提防别人。
许多类似问题中建议的 itertools.zip_longest
非常接近我想要的用例,因为它有一个 fillvalue
参数,可以填充较长的对。但是,这只需要一个值,而不是返回到较短列表中的第一个值。
请注意:在我的用例中,一个列表总是比另一个列表短得多,这可能允许走捷径,但通用解决方案也会令人兴奋!
您可以使用 itertools.cycle()
with zip
来获得所需的行为。
正如 itertools.cycle()
文档所说,它:
Make an iterator returning elements from the iterable and saving a copy of each. When the iterable is exhausted, return elements from the saved copy.
例如:
>>> l1 = ['a', 'b', 'c']
>>> l2 = ['x', 'y']
>>> from itertools import cycle
>>> zip(l1, cycle(l2))
[('a', 'x'), ('b', 'y'), ('c', 'x')]
由于在您的情况下,l1
和 l2
的长度可能会有所不同,因此您的通用 fn()
应该是这样的:
from itertools import cycle
def fn(l1, l2):
return zip(l1, cycle(l2)) if len(l1) > len(l2) else zip(cycle(l1), l2)
样本运行:
>>> l1 = ['a', 'b', 'c']
>>> l2 = ['x', 'y']
# when second parameter is shorter
>>> fn(l1, l2)
[('a', 'x'), ('b', 'y'), ('c', 'x')]
# when first parameter is shorter
>>> fn(l2, l1)
[('x', 'a'), ('y', 'b'), ('x', 'c')]
如果您不确定哪个最短,next
it.cycle
两个列表中最长的len
:
def fn(l1, l2):
return (next(zip(itertools.cycle(l1), itertoools.cycle(l2))) for _ in range(max((len(l1), len(l2)))))
>>> list(fn(l1, l2))
[('a', 'x'), ('a', 'x'), ('a', 'x')]
itertools.cycle
将无限重复该列表。然后,zip
将两个无限列表放在一起得到你想要的循环,但无限重复。所以现在,我们需要 trim 它的大小合适。 max((len(l1), len(l2)))
将找到两个列表中最长的长度,然后 next
无限迭代,直到找到正确的长度。请注意,此 returns 是一个生成器,因此要获得您想要的输出,请使用 list
吃掉函数。
如果我有两个不同长度的可迭代对象,我怎样才能最干净地将它们配对,重新使用较短的值,直到消耗掉较长的所有值?
例如,给定两个列表
l1 = ['a', 'b', 'c']
l2 = ['x', 'y']
最好有一个函数 fn()
生成对:
>>> fn(l1, l2)
[('a', 'x'), ('b', 'y'), ('c', 'x')]
我发现我可以编写一个函数来执行此操作
def fn(l1, l2):
if len(l1) > len(l2):
return [(v, l2[i % len(l2)]) for i, v in enumerate(l1)]
return [(l1[i % len(l1)], v) for i, v in enumerate(l2)]
>>> fn(l1, l2)
[('a', 'x'), ('b', 'y'), ('c', 'x')]
>>> l2 = ['x', 'y', 'z', 'w']
>>> fn(l1,l2)
[('a', 'x'), ('b', 'y'), ('c', 'z'), ('a', 'w')]
但是,我很贪心,很好奇还有什么其他方法存在?所以我可能select最明显和优雅的,并提防别人。
许多类似问题中建议的itertools.zip_longest
非常接近我想要的用例,因为它有一个 fillvalue
参数,可以填充较长的对。但是,这只需要一个值,而不是返回到较短列表中的第一个值。
请注意:在我的用例中,一个列表总是比另一个列表短得多,这可能允许走捷径,但通用解决方案也会令人兴奋!
您可以使用 itertools.cycle()
with zip
来获得所需的行为。
正如 itertools.cycle()
文档所说,它:
Make an iterator returning elements from the iterable and saving a copy of each. When the iterable is exhausted, return elements from the saved copy.
例如:
>>> l1 = ['a', 'b', 'c']
>>> l2 = ['x', 'y']
>>> from itertools import cycle
>>> zip(l1, cycle(l2))
[('a', 'x'), ('b', 'y'), ('c', 'x')]
由于在您的情况下,l1
和 l2
的长度可能会有所不同,因此您的通用 fn()
应该是这样的:
from itertools import cycle
def fn(l1, l2):
return zip(l1, cycle(l2)) if len(l1) > len(l2) else zip(cycle(l1), l2)
样本运行:
>>> l1 = ['a', 'b', 'c']
>>> l2 = ['x', 'y']
# when second parameter is shorter
>>> fn(l1, l2)
[('a', 'x'), ('b', 'y'), ('c', 'x')]
# when first parameter is shorter
>>> fn(l2, l1)
[('x', 'a'), ('y', 'b'), ('x', 'c')]
如果您不确定哪个最短,next
it.cycle
两个列表中最长的len
:
def fn(l1, l2):
return (next(zip(itertools.cycle(l1), itertoools.cycle(l2))) for _ in range(max((len(l1), len(l2)))))
>>> list(fn(l1, l2))
[('a', 'x'), ('a', 'x'), ('a', 'x')]
itertools.cycle
将无限重复该列表。然后,zip
将两个无限列表放在一起得到你想要的循环,但无限重复。所以现在,我们需要 trim 它的大小合适。 max((len(l1), len(l2)))
将找到两个列表中最长的长度,然后 next
无限迭代,直到找到正确的长度。请注意,此 returns 是一个生成器,因此要获得您想要的输出,请使用 list
吃掉函数。