【问题标题】:Stepping with multiple values while slicing an array in Python在 Python 中对数组进行切片时单步执行多个值
【发布时间】:2018-11-11 19:36:00
【问题描述】:

我试图在遍历数组的每个 n 元素时获取 m 值。例如,对于 m = 2 和 n = 5,并且给定

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

我想找回

b = [1, 2, 6, 7]

有没有办法使用切片来做到这一点?我可以使用嵌套列表理解来做到这一点,但我想知道是否有办法只使用索引来做到这一点。供参考,列表推导方式为:

 b = [k for j in [a[i:i+2] for i in range(0,len(a),5)] for k in j]

【问题讨论】:

  • 当列表长度有余数时会发生什么?
  • 0 需要显式版本标签,已移除

标签: python arrays list


【解决方案1】:

使用 itertools 你可以得到一个迭代器:

from itertools import compress, cycle

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
n = 5
m = 2

it = compress(a, cycle([1, 1, 0, 0, 0]))
res = list(it)

【讨论】:

  • 如果你更喜欢仿函数而不是生成器表达式:compress(a, cycle(map(m.__gt__, range(n)))) 甚至compress(a, cycle([True] * m + [False] * (n - m)))
  • 我更喜欢生成器表达式!我只是指向compress,这对于复杂的模式很有用。
【解决方案2】:

我知道递归并不流行,但是这样的东西有用吗?此外,不确定是否将递归添加到混合计数仅使用切片。

def get_elements(A, m, n):
    if(len(A) < m):
        return A
    else:
        return A[:m] + get_elements(A[n:], m, n)

A 是数组,m 和 n 在问题中定义。第一个 if 涵盖基本情况,其中您有一个长度小于您尝试检索的元素数量的数组,第二个 if 是递归情况。我对python有点陌生,如果它不能正常工作,请原谅我对语言的理解不佳,虽然我测试过它似乎工作正常。

【讨论】:

    【解决方案3】:

    问题陈述了数组,如果我们谈论的是 NumPy 数组,我们当然可以使用一些明显的 NumPy 技巧和一些不那么明显的技巧。我们当然可以使用slicing 在特定条件下获得输入的 2D 视图。

    现在,根据数组长度,我们称它为lm,我们将有三种情况:

    场景 #1 :l 可以被 n 整除

    我们可以使用切片和整形来获得输入数组的视图,从而获得恒定的运行时间。

    验证视图概念:

    In [108]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    
    In [109]: m = 2; n = 5
    
    In [110]: a.reshape(-1,n)[:,:m]
    Out[110]: 
    array([[1, 2],
           [6, 7]])
    
    In [111]: np.shares_memory(a, a.reshape(-1,n)[:,:m])
    Out[111]: True
    

    检查一个非常大的数组上的时间,因此持续的运行时声明:

    In [118]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    
    In [119]: %timeit a.reshape(-1,n)[:,:m]
    1000000 loops, best of 3: 563 ns per loop
    
    In [120]: a = np.arange(10000000)
    
    In [121]: %timeit a.reshape(-1,n)[:,:m]
    1000000 loops, best of 3: 564 ns per loop
    

    要获得扁平化版本:

    如果我们得到一个展平的数组作为输出,我们只需要使用.ravel()的展平操作,就像这样 -

    In [127]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    
    In [128]: m = 2; n = 5
    
    In [129]: a.reshape(-1,n)[:,:m].ravel()
    Out[129]: array([1, 2, 6, 7])
    

    时间表明,与其他帖子中的其他循环和矢量化 numpy.where 版本相比,它并不算太糟糕 -

    In [143]: a = np.arange(10000000)
    
    # @Kevin's soln
    In [145]: %timeit [x for i,x in enumerate(a) if i%n < m]
    1 loop, best of 3: 1.23 s per loop
    
    # @jpp's soln
    In [147]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
    10 loops, best of 3: 145 ms per loop
    
    In [144]: %timeit a.reshape(-1,n)[:,:m].ravel()
    100 loops, best of 3: 16.4 ms per loop
    

    场景 #2 :l 不能被 n 整除,但组以一个完整的结尾结束

    我们使用 np.lib.stride_tricks.as_strided 使用非显而易见的 NumPy 方法,该方法允许超出内存块边界(因此我们需要注意不要写入这些边界)以促进使用 slicing 的解决方案。实现看起来像这样 -

    def select_groups(a, m, n):
        a = np.asarray(a)
        strided = np.lib.stride_tricks.as_strided
    
        # Get params defining the lengths for slicing and output array shape    
        nrows = len(a)//n
        add0 = len(a)%n
        s = a.strides[0]
        out_shape = nrows+int(add0!=0),m
    
        # Finally stride, flatten with reshape and slice
        return strided(a, shape=out_shape, strides=(s*n,s))
    

    验证输出是否为 view 的示例运行 -

    In [151]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
    
    In [152]: m = 2; n = 5
    
    In [153]: select_groups(a, m, n)
    Out[153]: 
    array([[ 1,  2],
           [ 6,  7],
           [11, 12]])
    
    In [154]: np.shares_memory(a, select_groups(a, m, n))
    Out[154]: True
    

    要获得扁平化版本,请附加.ravel()

    让我们做一些时间比较 -

    In [158]: a = np.arange(10000003)
    
    In [159]: m = 2; n = 5
    
    # @Kevin's soln
    In [161]: %timeit [x for i,x in enumerate(a) if i%n < m]
    1 loop, best of 3: 1.24 s per loop
    
    # @jpp's soln
    In [162]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
    10 loops, best of 3: 148 ms per loop
    
    In [160]: %timeit select_groups(a, m=m, n=n)
    100000 loops, best of 3: 5.8 µs per loop
    

    如果我们需要一个扁平化的版本,那还是不错的 -

    In [163]: %timeit select_groups(a, m=m, n=n).ravel()
    100 loops, best of 3: 16.5 ms per loop
    

    场景#3:l 不能被n 整除,并且组以不完整的一个结尾

    对于这种情况,我们需要在前面方法的基础上在末尾进行额外的切片,就像这样 -

    def select_groups_generic(a, m, n):
        a = np.asarray(a)
        strided = np.lib.stride_tricks.as_strided
    
        # Get params defining the lengths for slicing and output array shape    
        nrows = len(a)//n
        add0 = len(a)%n
        lim = m*(nrows) + add0
        s = a.strides[0]
        out_shape = nrows+int(add0!=0),m
    
        # Finally stride, flatten with reshape and slice
        return strided(a, shape=out_shape, strides=(s*n,s)).reshape(-1)[:lim]
    

    示例运行 -

    In [166]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
    
    In [167]: m = 2; n = 5
    
    In [168]: select_groups_generic(a, m, n)
    Out[168]: array([ 1,  2,  6,  7, 11])
    

    时间安排 -

    In [170]: a = np.arange(10000001)
    
    In [171]: m = 2; n = 5
    
    # @Kevin's soln
    In [172]: %timeit [x for i,x in enumerate(a) if i%n < m]
    1 loop, best of 3: 1.23 s per loop
    
    # @jpp's soln
    In [173]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
    10 loops, best of 3: 145 ms per loop
    
    In [174]: %timeit select_groups_generic(a, m, n)
    100 loops, best of 3: 12.2 ms per loop
    

    【讨论】:

      【解决方案4】:

      还有其他方法可以做到这一点,在某些情况下它们都有优势,但没有一个是“只是切片”。


      最通用的解决方案可能是对您的输入进行分组,对组进行切片,然后将切片展平。此解决方案的一个优点是您可以惰性执行此操作,而无需构建大型中间列表,并且您可以对任何可迭代对象执行此操作,包括惰性迭代器,而不仅仅是列表。

      # from itertools recipes in the docs
      def grouper(iterable, n, fillvalue=None):
          "Collect data into fixed-length chunks or blocks"
          # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
          args = [iter(iterable)] * n
          return itertools.zip_longest(*args, fillvalue=fillvalue)
      groups = grouper(a, 5)
      truncated = (group[:2] for group in groups)
      b = [elem for group in truncated for elem in group]
      

      你可以将它转换成一个非常简单的单行,虽然你仍然需要 grouper 函数:

      b = [elem for group in grouper(a, 5) for elem in group[:2]]
      

      另一种选择是构建索引列表,并使用itemgetter 获取所有值。对于更复杂的函数,这可能比“每 5 个中的前 2 个”更具可读性,但对于像您使用这样简单的东西,它可能不太可读:

      indices = [i for i in range(len(a)) if i%5 < 2]
      b = operator.itemgetter(*indices)(a)
      

      ……可以变成单行:

      b = operator.itemgetter(*[i for i in range(len(a)) if i%5 < 2])(a)
      

      您可以通过编写自己的itemgetter 版本来结合这两种方法的优点,该版本采用惰性索引迭代器——我不会展示,因为您可以通过编写一个采用索引过滤器的版本来做得更好代替函数:

      def indexfilter(pred, a):
          return [elem for i, elem in enumerate(a) if pred(i)]
      b = indexfilter((lambda i: i%5<2), a)
      

      (要使indexfilter 变得懒惰,只需将括号替换为括号即可。)

      …或者,作为一个单行:

      b = [elem for i, elem in enumerate(a) if i%5<2]
      

      我认为最后一个可能是最易读的。它适用于任何可迭代的对象,而不仅仅是列表,它可以变得懒惰(同样,只需用括号替换括号)。但我仍然不认为它比您最初的理解更简单,而且它不仅仅是切片。

      【讨论】:

        【解决方案5】:

        简而言之,不,你不能。但是您可以使用itertools 来消除对中间列表的需要:

        from itertools import chain, islice
        
        res = list(chain.from_iterable(islice(a, i, i+2) for i in range(0, len(a), 5)))
        
        print(res)
        
        [1, 2, 6, 7]
        

        借用@Kevin 的逻辑,如果你想要一个矢量化的解决方案来避免for 循环,你可以使用第三方库numpy

        import numpy as np
        
        m, n = 2, 5
        a = np.array(a)  # convert to numpy array
        res = a[np.where(np.arange(a.shape[0]) % n < m)]
        

        【讨论】:

          【解决方案6】:

          我同意 wim 的观点,你不能只用切片来做到这一点。但是你可以只用一个列表理解来做到这一点:

          >>> [x for i,x in enumerate(a) if i%n < m]
          [1, 2, 6, 7]
          

          【讨论】:

          • 替代且没有除余但沿同一行:[x for i, x in zip(itertools.cycle(range(n)), a) if i &lt; m]
          【解决方案7】:

          不,切片是不可能的。切片仅支持开始、停止和步进 - 无法使用大小大于 1 的“组”来表示步进。

          【讨论】:

            猜你喜欢
            • 2013-06-21
            • 1970-01-01
            • 2015-10-15
            • 2016-10-22
            • 2016-04-03
            • 1970-01-01
            • 1970-01-01
            • 2016-02-20
            • 2015-11-22
            相关资源
            最近更新 更多