迭代不同长度的一对迭代的最干净的方法,包装较短的可迭代?

时间:2017-12-29 19:23:30

标签: python python-3.x 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')]

然而,我贪婪并且好奇还有其他什么方法存在?所以我可以选择最明显和优雅的,并且对其他方法保持警惕。

许多类似问题中提出的

itertools.zip_longest非常接近我想要的用例,因为它有一个fillvalue参数,用于填充较长的对。但是,这只需要一个值,而不是回到较短列表中的第一个值。

作为一个注释:在我的用例中,一个列表总是比另一个列表短得多,这可能允许快捷方式,但通用解决方案也会令人兴奋!

2 个答案:

答案 0 :(得分:1)

您可以将itertools.cycle()zip一起使用以获得所需的行为。

正如itertools.cycle()文件所说:

  

使迭代器从iterable返回元素并保存每个元素的副本。当iterable耗尽时,返回保存副本中的元素。

例如:

>>> l1 = ['a', 'b', 'c']
>>> l2 = ['x', 'y']

>>> from itertools import cycle
>>> zip(l1, cycle(l2))
[('a', 'x'), ('b', 'y'), ('c', 'x')]

因为在您的情况下,l1l2的长度可能会有所不同,您的通用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')]

答案 1 :(得分:-1)

如果您不确定哪一个是最短的,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来使用该函数。