这个问题在这里已有答案:
如果我有两个不同长度的迭代,我怎样才能最干净地配对它们,重新使用较短的值,直到消耗掉更长的所有值?
例如,给出两个列表
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')]
但是,我很贪婪并且好奇其他方法是什么?所以我可以选择最明显和优雅,并警惕其他人。
itertools.zip_longest
在很多类似的问题中提出的非常接近我想要的用例,因为它有一个fillvalue
参数,它将填充较长的对。但是,这只需要一个值,而不是回绕到较短列表中的第一个值。
作为一个注释:在我的用例中,一个列表总是比另一个列表短得多,这可能允许快捷方式,但通用解决方案也会令人兴奋!
您可以使用itertools.cycle()
和zip
来获得所需的行为。
正如itertools.cycle()
文件所说,它:
使迭代器返回迭代中的元素并保存每个元素的副本。当iterable耗尽时,返回保存副本中的元素。
例如:
>>> 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
将两个无限列表放在一起,以获得您想要的循环,但无限重复。所以现在,我们需要将其修剪到合适的尺寸。 max((len(l1), len(l2)))
将找到两个列表中最长的长度,然后next
无限可迭代,直到你达到正确的长度。请注意,这将返回一个生成器,因此要获得您想要的输出,请使用list
来使用该函数。