问题陈述了数组,如果我们谈论的是 NumPy 数组,我们当然可以使用一些明显的 NumPy 技巧和一些不那么明显的技巧。我们当然可以使用slicing 在特定条件下获得输入的 2D 视图。
现在,根据数组长度,我们称它为l 和m,我们将有三种情况:
场景 #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