【问题标题】:How to iterate "n-wise" over an iterator efficiently如何有效地在迭代器上迭代“n-wise”
【发布时间】:2019-06-14 06:26:11
【问题描述】:

可能是重复的,但我找不到任何东西。

我有一个很长的迭代器(10000 个项目),我需要一次迭代大约 500 个项目。所以如果我的迭代器是range(10000),它看起来像这样:

Iteration #1: 0, 1, 2, ... 497, 498, 499
Iteration #2: 1, 2, 3, ... 498, 499, 500
Iteration #3: 2, 3, 4, ... 499, 500, 501
Iteration #4: 3, 4, 5, ... 500, 501, 502
...
Iteration #9500: 9499, 9500, 9501 ... 9996, 9997, 9998
Iteration #9501: 9500, 9501, 9502 ... 9997, 9998, 9999

等等。有这个方法:

def nwise_slice(lst, n):
    for i in range(len(lst) - n + 1):
        yield lst[i:i + n]

但是,这不适用于惰性迭代器。我尝试使用迭代器创建一个解决方案,并改编自 itertools pairwiseconsume 配方(参见 here)来创建这个:

import itertools

def nwise_iter(lst, n):
    iters = itertools.tee(lst, n)
    for idx, itr in enumerate(iters):
        next(itertools.islice(itr, idx, idx), None)

    for group in zip(*iters):
        yield group

也是一样的(尽管会产生 tuple 而不是 list,这对我来说并不重要)。我也相信它不会产生很多不必要的切片。该解决方案适用于不可切片的迭代器,例如文件(我计划使用)。但是,itertools 解决方案的速度要慢 2 倍:

In [4]: %timeit list(nwise_slice(list(range(10000)), 500))
46.9 ms ± 729 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [5]: %timeit list(nwise_iter(list(range(10000)), 500))
102 ms ± 3.95 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

我不想为了利用slice 方法而将所有测试数据加载到内存中。有没有更有效的方法来解决这个问题?

【问题讨论】:

  • 查看我的answer 中的grouper() 函数。
  • @martineau 谢谢。不幸的是,这会迭代 (1, 2, 3), (4, 5, 6) 而不是 (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)
  • 您是否需要迭代器的结果在迭代推进时保持有效?您是否需要通过索引对每个子序列进行 O(1) 访问?
  • @DavisHerring 不,所有这些都是不需要的。我有能力“搞砸”原来的迭代器。
  • 可以使用滑动窗口算法。不是更快,而是更简洁,看more_itertools librarylist(more_itertools.windowed(range(10000), 500))

标签: python python-3.x list iterator itertools


【解决方案1】:

使用deque 到“memoize”你的项目怎么样?

from collections import deque

def nwise_slice(it, n):
    deq = deque((), n)
    for x in it:
        deq.append(x)
        if len(deq)==n: yield deq

my_range = range(8)
for sub in nwise_slice(my_range, 5):
    print(sub)
# =>
# deque([0, 1, 2, 3, 4], maxlen=5)
# deque([1, 2, 3, 4, 5], maxlen=5)
# deque([2, 3, 4, 5, 6], maxlen=5)
# deque([3, 4, 5, 6, 7], maxlen=5)

【讨论】:

  • 感谢您的回答。幸运的是,它非常快。但是,我得到了不正确的结果 - 它每次都会产生相同的双端队列。 (另外,lst[0:n] 在迭代器上会失败,但我认为这可以用islice 解决?)。
  • 我想主要的问题是我有点像 Python 的初学者。无论如何,我添加了我的测试代码,也许你能够弄清楚我是否使用不正确,或者我的用例和你的用例之间的区别在哪里。
  • 有趣:迭代它很好,但调用 list(nwise_slice(my_range, 5)) 会破坏它。嗯……
  • 好吧,list() 想要一个项目生成器,而不是像 nwise_slice() 这样的迭代器生成器
  • @WalterTross 没有它list(nwise_slice(x, 5)) 将是同一对象的列表。这是出乎意料的。可以像itertools.groupby 那样线性使用结果,但你必须知道生成器不会返回副本并自己制作[(k, list(g)) for k,g in groupby(...)],因为对象或迭代器在下一个不再有效迭代。可以将返回结果包装在一个可失效的代理中,而不是复制,当控制权返回到生成器时,该代理将变为无效。
猜你喜欢
  • 2014-07-19
  • 2012-02-17
  • 2013-02-14
  • 2016-03-30
  • 1970-01-01
  • 2012-12-11
  • 2016-07-24
  • 2014-01-19
相关资源
最近更新 更多