如您所见,numpy 不适合这类问题,至少在我看来不是。这是我可能用 cython 或 C 编写的东西,并与我的其余 python 代码粘合在一起。
一个简单的迭代解决方案将花费values 大小的线性时间。我能想到的使用 numpy 的最佳解决方案需要排序,因此需要 O(n log n)。对于少量输入,它仍然会比纯 Python 中的迭代解决方案更快,但渐近实际上应该更糟。
无论如何,代码如下:
def numpy_assign(values, row, col):
# arrange the values so that entries are contiguous
ix = np.ravel_multi_index((row, col), (2,2))
sort_ix = np.argsort(ix)
ix = ix[sort_ix]
values = values[sort_ix]
# now we need to construct the funny index argument
# to the `reduceat` ufunc method
reduce_ix = np.where(np.diff(ix))[0] + 1
reduce_ix = np.insert(reduce_ix, 0, 0)
# reduce the amounts
amount = np.add.reduceat(values, reduce_ix).reshape(2,2)
# and count the number of times we've done so
_, counts = np.unique(ix, return_counts=True)
counts = counts.reshape(2,2)
return amount, counts
现在,如果您的 python 发行版中有 numba(我只是使用 Anaconda 发行版的学术许可证附带的标准安装),那么我们实际上可以做得更好而不需要太多额外的工作:
import numba
@numba.autojit
def inner_loop(row, col, values, number, amount):
for i in range(len(row)):
r = row[i]
c = col[i]
v = values[i]
amount[r, c] += v
number[r, c] += 1
def numba_assign(row, col, values):
number = np.zeros([2,2], np.float)
amount = np.zeros([2,2], np.float)
inner_loop(row, col, values, number, amount)
return amount, number
这能快多少?在 1e6 个条目的随机输入上,我们得到时间:
>>> %timeit loop_assign(row, col, values)
1 loops, best of 3: 1.35 s per loop
>>> %timeit numpy_assign(row, col, values)
10 loops, best of 3: 81 ms per loop
>>> %timeit numba_assign(row, col, values)
100 loops, best of 3: 5.33 ms per loop
loop_assign 是您的原始代码。请注意,numba_assign 不仅运行速度比 numpy 解决方案快得多,而且它不需要排序,因此它具有线性渐近复杂度,这意味着它可以更好地扩展。