import numpy as np
def alt(a, end, window, start=0, step=1):
bin_starts = np.arange(start, end+1-window, step)
bin_ends = bin_starts + window
last_index = np.searchsorted(a, bin_ends, side='right')
first_index = np.searchsorted(a, bin_starts, side='left')
return last_index - first_index
def sliding_count(a, end, window, start=0, step=1):
bins = [(x, x + window) for x in range(start, (end + 1) - window, step)]
counts = np.zeros(len(bins))
for i, rng in enumerate(bins):
count = len(a[np.where(np.logical_and(a>=rng[0], a<=rng[1]))])
counts[i] = count
return counts
a = np.array([1, 5, 8, 11, 14, 19])
end = 20
window = 10
print(sliding_count(a, end, window))
print(alt(a, end, window))
alt的工作原理:
生成区间(bin)的起始值和结束值:
In [73]: bin_starts = np.arange(start, end+1-window, step); bin_starts
Out[73]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
In [74]: bin_ends = bin_starts + window; bin_ends
Out[74]: array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
由于a
已经按顺序排列,因此可以使用np.searchsorted
来查找每个值在bin_starts
和bin_ends
中的第一个和最后一个索引位置:
In [75]: last_index = np.searchsorted(a, bin_ends, side='right'); last_index
Out[75]: array([3, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6])
In [76]: first_index = np.searchsorted(a, bin_starts, side='left'); first_index
Out[76]: array([0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3])
count
就是索引之间的差值:
In [77]: last_index - first_index
Out[77]: array([3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3])
这里是一个perfplot,比较了alt
和sliding_count
在a
长度的函数中的性能:
import perfplot
def make_array(N):
a = np.random.randint(10, size=N)
a = a.cumsum()
return a
def using_sliding(a):
return sliding_count(a, end, window)
def using_alt(a):
return alt(a, end, window)
perfplot.show(
setup=make_array,
kernels=[using_sliding, using_alt],
n_range=[2**k for k in range(22)],
logx=True,
logy=True,
xlabel='len(a)')
Perfplot还会检查using_sliding
返回的值是否等于using_alt
返回的值。
Matt Timmermans提出的想法,即“从该bin的计数中减去position_in_a
”,启发了这个解决方案。
range
返回一个对象,你可以在其上执行x in range(...)
并获得真/假的结果。 - Nullmana
中的每个元素迭代一次bins
。 - Michael Hall