【问题标题】:Iterate over a ‘window’ of adjacent elements in Python在 Python 中遍历相邻元素的“窗口”
【发布时间】:2011-10-23 07:37:37
【问题描述】:

这更多的是优雅和性能的问题,而不是“根本怎么做”,所以我只展示代码:

def iterate_adjacencies(gen, fill=0, size=2, do_fill_left=True,
  do_fill_right=False):
    """ Iterates over a 'window' of `size` adjacent elements in the supploed
    `gen` generator, using `fill` to fill edge if `do_fill_left` is True
    (default), and fill the right edge (i.e.  last element and `size-1` of
    `fill` elements as the last item) if `do_fill_right` is True.  """
    fill_size = size - 1
    prev = [fill] * fill_size
    i = 1
    for item in gen:  # iterate over the supplied `whatever`.
        if not do_fill_left and i < size:
            i += 1
        else:
            yield prev + [item]
        prev = prev[1:] + [item]
    if do_fill_right:
        for i in range(fill_size):
            yield prev + [fill]
            prev = prev[1:] + [fill]

然后问:是否已经有这样的功能?如果不能,您能否以更好(即更整洁和/或更快速)的方式做同样的事情?

编辑:

结合@agf、@FogleBird、@senderle 的答案的想法,得到的一段看起来有点整洁的代码是:

def window(seq, size=2, fill=0, fill_left=True, fill_right=False):
    """ Returns a sliding window (of width n) over data from the iterable:
      s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...
    """
    ssize = size - 1
    it = chain(
      repeat(fill, ssize * fill_left),
      iter(seq),
      repeat(fill, ssize * fill_right))
    result = tuple(islice(it, size))
    if len(result) == size:  # `<=` if okay to return seq if len(seq) < size
        yield result
    for elem in it:
        result = result[1:] + (elem,)
        yield result

【问题讨论】:

  • 您可能想更详细地解释一下这对文字有什么作用。
  • 您能否提供样本输入和预期输出的样本?更容易理解你的函数在做什么。
  • 运行代码并查看比从文档字符串中弄清楚要快,因为措辞不是很清楚,所以我建议他把它写得更清楚。
  • @HoverHell - 非常非常好。您应该将其作为答案发布并接受。我将如何以不同的方式表达文档字符串?我会把它分解成几个简短的陈述句来描述它的作用和每个论点。
  • @HoverHell: 1) 是的,请发布您的新代码作为答案。 2)This question and its answer解释如何在numpy中创建滚动窗口。

标签: python


【解决方案1】:

本页展示了如何使用itertools 实现滑动窗口。 http://docs.python.org/release/2.3.5/lib/itertools-example.html

def window(seq, n=2):
    "Returns a sliding window (of width n) over data from the iterable"
    "   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...                   "
    it = iter(seq)
    result = tuple(islice(it, n))
    if len(result) == n:
        yield result    
    for elem in it:
        result = result[1:] + (elem,)
        yield result

示例输出:

>>> list(window(range(10)))
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)]

如果需要,您需要将其更改为填充左右。

【讨论】:

    【解决方案2】:

    这是我的版本,保持签名不变。之前看过itertools的菜谱,写这篇之前没看。

    from itertools import chain
    from collections import deque
    
    def ia(gen, fill=0, size=2, fill_left=True, fill_right=False):
        gen, ssize = iter(gen), size - 1
        deq = deque(chain([fill] * ssize * fill_left,
                          (next(gen) for _ in xrange((not fill_left) * ssize))),
                    maxlen = size)
        for item in chain(gen, [fill] * ssize * fill_right):
            deq.append(item)
            yield deq
    

    编辑:在发布此问题之前,我也没有看到您的问题。

    编辑 2:已修复。我曾尝试使用一个 chain 来实现,但这个设计需要两个。

    编辑 3:正如@senderle 所说,仅将其用作生成器,不要用 list 包装它或累积输出,因为它会重复产生相同的可变项。

    【讨论】:

    • 返回一个元组不是更好吗? list(ia(range(10), 'a', fill_left=True, fill_right=True)) 的结果是对同一个双端队列的引用列表,它们都处于相同的状态。此外,您可以使用islice 构造双端队列,然后遍历 gen 的其余部分,而不是调用 chain 两次。
    • 这只是itertools中给出的解决方案。我对复制它不感兴趣。
    • 是吗?我在itertools 的任何地方都看不到这一点。让我重述一下。反复yielding 同一个可变对象会产生意想不到的结果。例如,list(ia(range(2), 'x', 2, True, True)) 的输出是[deque([1, 'x'], maxlen=2), deque([1, 'x'], maxlen=2), deque([1, 'x'], maxlen=2)]。我认为返回双端队列内容的副本而不是仅仅返回双端队列是有意义的。最快的方法可能是使用tuple
    • 我明白了。但是,如果您要产生tuples 并使用islice,那么@HoverHell 给出的解决方案基于itertoolschainrepeat 填充的解决方案会更好。不值得把它改成那样。最好把它留到你真的想把它用作生成器的时候。
    • 是的,好的,我明白你现在在说什么了。事实上,在完成some timings 之后,我不得不承认你在所有方面都是完全正确的。
    【解决方案3】:

    好的,经过我的理解,这是window_iter_fill 的非荒谬版本。我以前的版本(在编辑中可见)很糟糕,因为我忘记使用izip。不知道我在想什么。使用 izip,这是可行的,事实上,对于小输入来说,这是最快的选择!

    def window_iter_fill(gen, size=2, fill=None):
        gens = (chain(repeat(fill, size - i - 1), gen, repeat(fill, i))
                for i, gen in enumerate(tee(gen, size)))
        return izip(*gens)
    

    这也适用于生成元组,但速度没有那么快。

    def window_iter_deque(it, size=2, fill=None, fill_left=False, fill_right=False):
        lfill = repeat(fill, size - 1 if fill_left else 0)
        rfill = repeat(fill, size - 1 if fill_right else 0)
        it = chain(lfill, it, rfill)
        d = deque(islice(it, 0, size - 1), maxlen=size)
        for item in it:
            d.append(item)
            yield tuple(d)
    

    HoverHell 的最新解决方案仍然是高输入的最佳元组生成解决方案。

    一些时间安排:

    Arguments: [xrange(1000), 5, 'x', True, True]
    
    ==============================================================================
      window               HoverHell's frankeniter           :  0.2670ms [1.91x]
      window_itertools     from old itertools docs           :  0.2811ms [2.02x]
      window_iter_fill     extended `pairwise` with izip     :  0.1394ms [1.00x]
      window_iter_deque    deque-based, copying              :  0.4910ms [3.52x]
      ia_with_copy         deque-based, copying v2           :  0.4892ms [3.51x]
      ia                   deque-based, no copy              :  0.2224ms [1.60x]
    ==============================================================================
    

    缩放行为:

    Arguments: [xrange(10000), 50, 'x', True, True]
    
    ==============================================================================
      window               HoverHell's frankeniter           :  9.4897ms [4.61x]
      window_itertools     from old itertools docs           :  9.4406ms [4.59x]
      window_iter_fill     extended `pairwise` with izip     :  11.5223ms [5.60x]
      window_iter_deque    deque-based, copying              :  12.7657ms [6.21x]
      ia_with_copy         deque-based, copying v2           :  13.0213ms [6.33x]
      ia                   deque-based, no copy              :  2.0566ms [1.00x]
    ==============================================================================
    

    agf 的 deque-yielding 解决方案对于大型输入来说非常快——看起来像 O(n) 而不是 O(n, m),其中 n 是迭代器的长度,m 是window——因为它不必遍历每个窗口。但我仍然认为在一般情况下产生一个元组更有意义,因为调用函数可能只是要遍历双端队列;这只是计算负担的转移。较大程序的渐近行为应该保持不变。

    不过,在某些特殊情况下,deque-yielding 版本可能会更快。

    更多时间基于 HoverHell 的测试结构。

    >>> import testmodule
    >>> kwa = dict(gen=xrange(1000), size=4, fill=-1, fill_left=True, fill_right=True)
    >>> %timeit -n 1000 [a + b + c + d for a, b, c, d in testmodule.window(**kwa)]
    1000 loops, best of 3: 462 us per loop
    >>> %timeit -n 1000 [a + b + c + d for a, b, c, d in testmodule.ia(**kwa)]
    1000 loops, best of 3: 463 us per loop
    >>> %timeit -n 1000 [a + b + c + d for a, b, c, d in testmodule.window_iter_fill(**kwa)]
    1000 loops, best of 3: 251 us per loop
    >>> %timeit -n 1000 [sum(x) for x in testmodule.window(**kwa)]
    1000 loops, best of 3: 525 us per loop
    >>> %timeit -n 1000 [sum(x) for x in testmodule.ia(**kwa)]
    1000 loops, best of 3: 462 us per loop
    >>> %timeit -n 1000 [sum(x) for x in testmodule.window_iter_fill(**kwa)]
    1000 loops, best of 3: 333 us per loop
    

    总体而言,一旦您使用izipwindow_iter_fill 就会非常快,事实证明——尤其是对于小窗口。

    【讨论】:

    • 迭代一个双端队列比迭代一个元组更快,所以如果你要迭代,返回一个双端队列是有意义的。 Here's a recent answer 在其中我计时了一些窗口迭代器并考虑了双端队列与元组/列表的问题。
    • 有趣的是 window_itertools 有点慢。此外,如果您要迭代,则 deque 会更快,但如果您要解包,则 tuple 似乎更快。
    • 呃,实际上元组在解包时 似乎 更快 - 在这种情况下,双端队列的速度大致相同。请参阅我的回答了解一些时间。
    • @kindall,此外,如果您返回一个元组,您可能会迭代这些值两次。但我并不清楚——我的意思是,尽管ia 看起来 在上述时序中的渐近行为更好(只有~10x 而不是~50x 像其他所有时间一样慢)使用它的程序的渐近行为可能不会更好。因此,您实际上只是通过一个常数因子获得了加速。我不认为为更通用的迭代器付出的代价是不可接受的。当然,出于特殊目的,返回双端队列是一个不错的策略。
    • @kindall,@HoverHell,看来我是愚蠢的!我忘了使用izip——事实上,使用izip,产生元组是自动的,并且对于小输入产生最快的结果(在我的计算机上)。
    【解决方案4】:

    结果函数(来自问题的编辑),

    从@agf、@FogleBird、@senderle 的答案中获得灵感的弗兰肯尼特,得到的一段看起来有点整洁的代码是:

    from itertools import chain, repeat, islice
    
    def window(seq, size=2, fill=0, fill_left=True, fill_right=False):
        """ Returns a sliding window (of width n) over data from the iterable:
          s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...
        """
        ssize = size - 1
        it = chain(
          repeat(fill, ssize * fill_left),
          iter(seq),
          repeat(fill, ssize * fill_right))
        result = tuple(islice(it, size))
        if len(result) == size:  # `<=` if okay to return seq if len(seq) < size
            yield result
        for elem in it:
            result = result[1:] + (elem,)
            yield result
    

    关于双端队列/元组的一些性能信息:

    In [32]: kwa = dict(gen=xrange(1000), size=4, fill=-1, fill_left=True, fill_right=True)
    In [33]: %timeit -n 10000 [a+b+c+d for a,b,c,d in tmpf5.ia(**kwa)]
    10000 loops, best of 3: 358 us per loop
    In [34]: %timeit -n 10000 [a+b+c+d for a,b,c,d in tmpf5.window(**kwa)]
    10000 loops, best of 3: 368 us per loop
    In [36]: %timeit -n 10000 [sum(x) for x in tmpf5.ia(**kwa)]
    10000 loops, best of 3: 340 us per loop
    In [37]: %timeit -n 10000 [sum(x) for x in tmpf5.window(**kwa)]
    10000 loops, best of 3: 432 us per loop
    

    但无论如何,如果是数字,那么 numpy 可能更可取。

    【讨论】:

      【解决方案5】:

      我很惊讶没有人采用简单的协程方法。

      from collections import deque
      
      
      def window(n, initial_data=None):
          if initial_data:
              win = deque(initial_data, n)
          else:
              win = deque(((yield) for _ in range(n)), n)
          while 1:
              side, val = (yield win)
              if side == 'left':
                  win.appendleft(val)
              else:
                  win.append(val)
      
      win = window(4)
      win.next()
      
      print(win.send(('left', 1)))
      print(win.send(('left', 2)))
      print(win.send(('left', 3)))
      print(win.send(('left', 4)))
      print(win.send(('right', 5)))
      
      ## -- Results of print statements --
      deque([1, None, None, None], maxlen=4)
      deque([2, 1, None, None], maxlen=4)
      deque([3, 2, 1, None], maxlen=4)
      deque([4, 3, 2, 1], maxlen=4)
      deque([3, 2, 1, 5], maxlen=4)
      

      【讨论】:

        猜你喜欢
        • 2011-10-26
        • 1970-01-01
        • 2016-11-12
        • 2021-02-21
        • 1970-01-01
        • 1970-01-01
        • 2012-10-17
        • 2019-08-24
        • 2020-03-09
        相关资源
        最近更新 更多