有没有一种优雅的方式通过迭代循环N次列表(类似于itertools.cycle但限制循环次数)?

14
我想通过迭代器重复(N次)地循环遍历列表,而不是实际上在内存中存储N个列表副本。是否有内置或优雅的方法可以做到这一点,而无需编写自己的生成器?
理想情况下,itertools.cycle(my_list)会有第二个参数来限制它循环的次数......但遗憾的是,并没有这样的运气。

我相信仅仅将列表乘以一个整数是不够好的,对吧?[1, 2, 3] * 4 - C2H5OH
@C2H5OH 这将创建列表的4个浅拷贝(不需要N个拷贝)。 - Casey Kuball
@Darthfett:确实。这就是为什么它是一个注释。但您会同意,这是最优雅的解决方案:-P - C2H5OH
6个回答

21
import itertools
it = itertools.chain.from_iterable(itertools.repeat([1, 2, 3], 5))
print(list(it))
# [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

Itertools 是一个很棒的库。 :)

正如评论中指出的那样,如果您正在使用迭代器(而不是列表或类似的集合),则在第一次迭代后迭代器会被耗尽,因此您需要使用稍微不同的方法来获得预期的结果。请注意,无限或过长的迭代器无法合理处理,因为您将不得不缓存结果(需要无限或过多的内存)。

这可能不是最有效的实现方式(tee 存储 iterable 的内容的 N 个副本):

import itertools
it = itertools.chain(*itertools.tee(iter([1, 2, 3]), 5))
print(list(it))
# [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

1
请注意,如果repeat的第一个参数是可消耗的迭代器,则此方法将无法正常工作。例如,list(itertools.chain.from_iterable(itertools.repeat(iter([1, 2, 3]), 2)))的结果为[1, 2, 3],而不是[1, 2, 3, 1, 2, 3]。这是因为迭代器在第一次迭代时就被消耗完了。 - user2846495
@user2846495,这是准确的。对于任何迭代器,真正的解决方案都不可能写出来,因为你需要缓存结果,这会导致任何无限生成器的问题(你最终会浪费无限量的内存)。 - Casey Kuball
对于任何有限的生成器,可以使用 itertools.tee 创建可迭代对象的副本,这将使得使用 list(itertools.chain(*itertools.tee(iter([1, 2, 3]), 5))) 实现变得相当简单。 - Casey Kuball
对于任何有限的生成器,可以使用itertools.tee来创建可迭代对象的副本,这样就可以很简单地实现了。例如,可以使用list(itertools.chain(*itertools.tee(iter([1, 2, 3]), 5)))来实现。 - Casey Kuball

13

其他的答案都很好。另一个解决方案是使用 islice。这样可以在任何时候中断循环:

>>> from itertools import islice, cycle
>>> l = [1, 2, 3]
>>> list(islice(cycle(l), len(l) * 3))
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> list(islice(cycle(l), 7))
[1, 2, 3, 1, 2, 3, 1]

不错,我不知道islice可以使用大于可迭代对象长度的值作为“stop”。 - Casey Kuball
3
@Darthfett,是的,没错。但这并不相关;cycle返回的可迭代对象是无限长的。 - senderle
在Python 3中,等价的写法是什么? - undefined

8
itertools.chain.from_iterable(iter(L) for x in range(N))

6

如果需要多次遍历列表,则这并不太麻烦。它会创建一个包含n个对my_list的引用的列表,所以如果n非常大,则最好使用Darthfelt的答案。

>>> import itertools as it
>>> it.chain(*[my_list]*n)

5
你说你不想写自己的生成器,但是生成器表达式可能是实现你想要的最简单和最有效的方式。它不需要任何函数调用或导入任何模块。itertools是一个很棒的模块,但在这种情况下可能并不必要。
some_list = [1, 2, 3]
cycles = 3
gen_expr = (elem for _ in xrange(cycles) for elem in some_list)

或者只是
(elem for _ in xrange(3) for elem in [1, 2, 3])

或者

for elem in (e for _ in xrange(3) for e in [1, 2, 3]):
    print "hoo-ray, {}!".format(elem)

我确实喜欢这些生成器表达式。对于这种模式,它们比我意识到的要更紧凑。我会把答案归功于@Darthfett,因为技术上我要求一个非自制的生成器,但如果我可以接受两个,我也会接受你的(可能还有其他人的 :-))。谢谢! - JJC

4

@Darthfett的答案记录在itertools recipes文档中,可以参考以下链接:

from itertools import chain, repeat


def ncycles(iterable, n):
    "Returns the sequence elements n times"
    return chain.from_iterable(repeat(tuple(iterable), n))


list(ncycles(["a", "b"], 3))
# ['a', 'b', 'a', 'b', 'a', 'b']

为了方便起见,我补充说明一下:more_itertools库为您实现了这个配方(以及许多其他配方):
import more_itertools as mit


list(mit.ncycles(["a", "b"], 3))
# ['a', 'b', 'a', 'b', 'a', 'b']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接