【问题标题】:Discontinuous slice in python listpython列表中的不连续切片
【发布时间】:2023-03-07 01:06:01
【问题描述】:

我正在寻找一种有效的方法来实现这一点,我认为这是一种类似切片的操作:

>>> mylist = range(100)
>>>magicslicer(mylist, 10, 20)
[0,1,2,3,4,5,6,7,8,9,30,31,32,33,34,35,36,37,38,39,60,61,62,63......,97,98,99]

想法是:切片获取 10 个元素,然后跳过 20 个元素,然后获取下一个 10,然后跳过下一个 20,依此类推。

如果可能的话,我认为我不应该使用循环,因为使用 slice 的原因(我猜)是为了在单个操作中有效地进行“提取”。

感谢阅读。

【问题讨论】:

    标签: python performance list slice


    【解决方案1】:

    itertools.compress(2.7/3.1 中的新功能)很好地支持这样的用例,尤其是与itertools.cycle 结合使用时:

    from itertools import cycle, compress
    seq = range(100)
    criteria = cycle([True]*10 + [False]*20) # Use whatever pattern you like
    >>> list(compress(seq, criteria))
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
    

    Python 2.7 时序(相对于 Sven 的显式列表理解):

    $ ./python -m timeit -s "a = range(100)" "[x for start in range(0, len(a), 30) for x in a[start:start+10]]"
    100000 loops, best of 3: 4.96 usec per loop
    
    $ ./python -m timeit -s "from itertools import cycle, compress" -s "a = range(100)" -s "criteria = cycle([True]*10 + [False]*20)" "list(compress(a, criteria))"
    100000 loops, best of 3: 4.76 usec per loop
    

    Python 3.2 时间安排(也与 Sven 的显式列表理解相关):

    $ ./python -m timeit -s "a = range(100)" "[x for start in range(0, len(a), 30) for x in a[start:start+10]]"
    100000 loops, best of 3: 7.41 usec per loop
    
    $ ./python -m timeit -s "from itertools import cycle, compress" -s "a = range(100)" -s "criteria = cycle([True]*10 + [False]*20)" "list(compress(a, criteria))"
    100000 loops, best of 3: 4.78 usec per loop
    

    可以看出,与 2.7 中的内联列表推导相比,它并没有太大的区别,但通过避免隐式嵌套范围的开销,在 3.2 中有很大帮助。

    如果目标是迭代结果序列而不是将其变成完全实现的列表,则在 2.7 中也可以看到类似的差异:

    $ ./python -m timeit -s "a = range(100)" "for x in (x for start in range(0, len(a), 30) for x in a[start:start+10]): pass"
    100000 loops, best of 3: 6.82 usec per loop
    $ ./python -m timeit -s "from itertools import cycle, compress" -s "a = range(100)" -s "criteria = cycle([True]*10 + [False]*20)" "for x in compress(a, criteria): pass"
    100000 loops, best of 3: 3.61 usec per loop
    

    对于特别长的模式,可以将模式表达式中的列表替换为 chain(repeat(True, 10), repeat(False, 20)) 之类的表达式,这样就不必在内存中完全创建它。

    【讨论】:

    • 不错!请注意,如果您要跳过很多值,显式列表推导仍然会更快,因为compress() 必须遍历所有这些值,而列表推导确实会跳过它们。
    • 是的,尽管compress() 方法的相反好处是它适用于任意迭代,甚至是不支持切片的迭代。
    • 哦,天哪,我创造了一个“怪物问题” :o) 这个答案是我一直在寻找的,因为我听到了“C 级”魔法词。我将不得不尽快对其进行测试。此外,这种直接从命令行使用python 的好方法对我来说是新的。谢谢。
    【解决方案2】:

    也许最好的方法是直截了当的方法:

    def magicslicer(seq, take, skip):
        return [x for start in range(0, len(seq), take + skip)
                  for x in seq[start:start + take]]
    

    我认为你无法避免循环。

    编辑:由于这被标记为“性能”,这里与a = range(100)的模解进行比较:

    In [2]: %timeit [x for start in range(0, len(a), 30)
                       for x in a[start:start + 10]]
    100000 loops, best of 3: 4.89 us per loop
    
    In [3]: %timeit [e for i, e in enumerate(a) if i % 30 < 10]
    100000 loops, best of 3: 14.8 us per loop
    

    【讨论】:

    • 太棒了!现在我们还有一个简单实用的timeit 使用示例!谢谢!
    • itertools.compress 允许将循环下推到 C 级别,并且与列表理解一样快,比 2.7 中的生成器表达式更快,也比 3.2 中的两者都快(由于隐式3.x 理解中的嵌套范围)。
    【解决方案3】:

    不幸的是,我认为切片无法做到这一点。我会使用list comprehensions解决问题

    >>> a = range(100)
    >>> a
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
     10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        ...
     90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
    >>> [e for i, e in enumerate(a) if i % 30 < 10]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
     30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
     60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
     90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
    

    【讨论】:

      【解决方案4】:

      我不知道您是否只使用数字,但如果您坚持使用 numpy.但是,只有当您的列表由扁平化的相等长度的子列表组成时,以下方法才有效。

      比较:

      import numpy as np
      from itertools import cycle, compress
      
      startList = list(range(0, 3000))
      startNpArray = np.linspace(0,2999,3000,dtype=np.int)
      
      def WithNumpy(seq, keep, skip):
          return seq.reshape((-1, keep+skip))[:,:keep+1].flatten()
      
      def WithItertools(seq, keep, skip):
          criteria = cycle([True]*keep + [False]* skip)
          return list(compress(seq, criteria))
      
      %timeit WithNumpy(startListNp, 10, 20)
      >>> 2.59 µs ± 48.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
      %timeit WithItertools(startList, 10, 20)
      >>> 33.5 µs ± 911 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
      

      【讨论】:

      • 对整形和切片的巧妙运用!很有启发性!
      【解决方案5】:

      我会使用循环:

      #!/usr/bin/env python
      
      
      def magicslicer(l, stepsize, stepgap):
          output = []
          i = 0
          while i<len(l):
              output += l[i:i+stepsize]
              i += stepsize + stepgap
          return output
      
      
      mylist = range(100)
      print magicslicer(mylist,10,20)
      

      【讨论】:

        【解决方案6】:
        >>>[mylist[start:start+10] for start in mylist[::30]]
        >>>[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]
        

        但我获得了一个列表列表:(

        【讨论】:

          【解决方案7】:
          mylist = range(100)
          
          otherlist = ['21','31','689','777','479','51','71','yut','poi','ger',
                       '11','61','789','zozozozo','8888','1']
          
          
          
          def magic_slicer(iterable,keep,throw):
                  it = iter(iterable).next
                  for n in xrange((len(iterable)//keep+throw)+1):
                          for i in xrange(keep):  yield it()
                          for i in xrange(throw):  it()
          
          print list(magic_slicer(mylist,10,20))
          print
          print list(magic_slicer(otherlist,2,3))
          
          
          print '__________________'
          
          
          def magic_slicer2(iterable,keep,throw):
                  return ( x for i,x in enumerate(iterable) if -1< i%(keep+throw)<keep) 
          
          print list(magic_slicer2(mylist,10,20))
          print
          print list(magic_slicer2(otherlist,2,3))
          

          结果

          [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
          
          ['21', '31', '51', '71', '11', '61', '1']
          __________________
          [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
          
          ['21', '31', '51', '71', '11', '61', '1']
          

          【讨论】:

            【解决方案8】:

            [x for x in range(100) if x%30 &lt; 10] 是另一种方法。但是,随着列表大小的增加,这可能会很慢。

            同一行的函数

            def magic_slice(n, no_elems, step):
                s = no_elems + step
                return [x for x in range(n) if x%s < no_elems]
            

            【讨论】:

            • 建议列表理解的人走在正确的轨道上。需要循环是正确的,但是列表推导将循环放在 Python 引擎中,它比显式循环更快。
            猜你喜欢
            • 1970-01-01
            • 2011-09-20
            • 2018-05-08
            • 1970-01-01
            • 2020-01-15
            • 1970-01-01
            • 2018-08-31
            • 2018-10-13
            相关资源
            最近更新 更多