【问题标题】:Iterate over n successive elements of list (with overlapping)迭代列表的 n 个连续元素(重叠)
【发布时间】:2016-11-04 05:19:37
【问题描述】:

itertools python 模块为迭代器实现了一些基本的构建块。正如他们所说,“它们形成了一个迭代器代数”。我期待,但我找不到使用该模块进行以下迭代的简洁方法。给定一个有序实数列表,例如

a = [1.0,1.5,2.0,2.5,3.0]

...返回一个新列表(或只是迭代)按一些n 值分组,比如2

b = [(1.0,1.5),(1.5,2.0),(2.0,2.5),(2.5,3.0)]

我发现这样做的方法如下。首先将列表一分为二,带有偶数和赔率索引:

even, odds = a[::2], a[1::2]

然后构造新列表:

b = [(even, odd) for even, odd in zip(evens, odds)]
b = sorted(b + [(odd, even) for even, odd in zip(evens[1:], odds)])

本质上,它类似于移动均值。

是否有简洁的方法(使用或不使用 itertools)?


PS:

应用程序

a 列表想象为实验期间发生的某些事件的时间戳集:

timestamp       event
47.8            1a
60.5            1b
67.4            2a
74.5            2b
78.5            1a
82.2            1b
89.5            2a
95.3            2b
101.7           1a
110.2           1b
121.9           2a
127.1           2b

...

此代码用于根据不同的时间窗口对这些事件进行分段。现在我对2连续事件之间的数据感兴趣; 'n > 2' 仅用于探索目的。

【问题讨论】:

    标签: python list iterator


    【解决方案1】:

    对于2,你可以这样做

    b = zip(a, a[1:])  # or list(zip(...)) on Python 3 if you really want a list
    

    对于固定的 n,技术类似:

    # n = 4
    b = zip(a, a[1:], a[2:], a[3:])
    

    对于变量 n,您可以压缩可变数量的切片,或者(特别是如果窗口大小接近 a 的大小)您可以使用切片直接获取窗口:

    b = zip(*[a[i:] for i in xrange(n)])
    # or
    b = [tuple(a[i:i+n]) for i in xrange(len(a)-n+1)]
    

    如果 a 不是列表,您可以从 itertools 文档中概括 pairwise 配方:

    import copy
    import itertools
    
    def nwise(iterable, n):
        # Make n tees at successive positions along the iterable.
        tees = list(itertools.tee(iterable, 1))
        for _ in xrange(n-1):
            tees.append(copy.copy(tees[-1]))
            next(tees[-1])
    
        return zip(*tees)
    

    【讨论】:

    • @glibdud:啊,你是对的。单个 tee 仍然包裹在一个单元素元组中。
    • 你的第二个例子应该是b = [tuple(a[i:i+n]) for i in xrange(len(a)-n+1)],除非我忽略了一些东西。它似乎缺少+1
    【解决方案2】:

    这正是pairwise itertools recipe 的用途,n=2 就是这样。

    from itertools import tee
    
    def pairwise(iterable):
        "s -> (s0,s1), (s1,s2), (s2, s3), ..."
        a, b = tee(iterable)
        next(b, None)
        return zip(a, b)
    

    演示

    >>> b = [1.0,1.5,2.0,2.5,3.0]
    >>> list(pairwise(b))
    [(1.0, 1.5), (1.5, 2.0), (2.0, 2.5), (2.5, 3.0)]
    

    如果您正在寻找可变组大小,请参阅user2357112's answer(我喜欢这种方法),或者更一般地,您可以实现滑动窗口迭代器并获取切片of which there are many approaches


    顺便说一句,您可以使用新的@987654328 切片(以控制重叠)不在链接问题上的可能性能不佳但有趣的单行窗口@ 语法来组合生成器。

    from itertools import tee, islice
    def roll_window(it, sz):
        yield from zip(*[islice(it, g, None) for g, it in enumerate(tee(it, sz))])
    

    演示

    >>> b = [1.0,1.5,2.0,2.5,3.0, 3.5, 4.0, 4.5]
    >>> list(islice(window(b, 3), None, None, 2))
    [(1.0, 1.5, 2.0), (2.0, 2.5, 3.0), (3.0, 3.5, 4.0)]
    

    【讨论】:

      【解决方案3】:

      使用生成器:

      def groupListByN(lst, n):
        for i in range(len(a)-n+1):
          yield lst[i:i+n]
      
      a = [1.0,1.5,2.0,2.5,3.0]
      myDoubleList = [group for group in groupListByN(a, 2)]
      myTripleList = [group for group in groupListByN(a, 3)]
      
      print(myDoubleList)
      print(myTripleList)
      

      结果:

      [[1.0, 1.5], [1.5, 2.0], [2.0, 2.5], [2.5, 3.0]]
      [[1.0, 1.5, 2.0], [1.5, 2.0, 2.5], [2.0, 2.5, 3.0]]
      

      我认为这个解决方案非常简洁

      【讨论】:

        【解决方案4】:

        如前所述,pairwise recipe 会重叠对。

        这个配方也在一个外部库中实现,more_itertools,以及其他有用的windowing tools

        import more_itertools as mit
        
        
        a = [1.0, 1.5, 2.0, 2.5, 3.0]
        
        list(mit.pairwise(a))
        # [(1.0, 1.5), (1.5, 2.0), (2.0, 2.5), (2.5, 3.0)]
        
        list(mit.windowed(a, n=2))
        # [(1.0, 1.5), (1.5, 2.0), (2.0, 2.5), (2.5, 3.0)]
        
        list(mit.stagger(a, offsets=(0, 1)))
        # [(1.0, 1.5), (1.5, 2.0), (2.0, 2.5), (2.5, 3.0)]
        

        注意,使用more_itertools.windowed,您可以控制n,滑动窗口的大小(如果需要,甚至可以通过step 参数控制重叠量)。此工具可能对您的探索有用。

        通过> pip install more_itertools安装这个库。

        【讨论】: