简短回答
def invert_permutation(p):
"""Return an array s with which np.array_equal(arr[p][s], arr) is True.
The array_like argument p must be some permutation of 0, 1, ..., len(p)-1.
"""
p = np.asanyarray(p)
s = np.empty_like(p)
s[p] = np.arange(p.size)
return s
在这里进行排序是过度设计了。 这只是一个单遍线性时间算法,内存需求恒定:
from __future__ import print_function
import numpy as np
p = np.array([3, 2, 0, 1])
s = np.empty(p.size, dtype=np.int32)
for i in np.arange(p.size):
s[p[i]] = i
print('s =', s)
以上代码输出
s = [2 3 1 0]
以下是关于以上for
循环的高效向量化问题。如果只想知道结论,请跳到该答案的末尾。
(原始回答时间为2014年8月27日;时间对于NumPy 1.8有效。随后使用NumPy 1.11进行了更新。)
单遍,线性时间算法预计比np.argsort
更快;有趣的是,上述for
循环的平凡向量化(s[p] = xrange(p.size)
, 请参见索引数组)实际上略慢于np.argsort
,只要p.size < 700000
(在我的机器上,您的情况可能会不同):
import numpy as np
def np_argsort(p):
return np.argsort(p)
def np_fancy(p):
s = np.zeros(p.size, p.dtype)
s[p] = xrange(p.size)
return s
def create_input(n):
np.random.seed(31)
indices = np.arange(n, dtype = np.int32)
return np.random.permutation(indices)
来自我的IPython笔记本:
p = create_input(700000)
%timeit np_argsort(p)
10 loops, best of 3: 72.7 ms per loop
%timeit np_fancy(p)
10 loops, best of 3: 70.2 ms per loop
最终渐进复杂度开始起作用(对于argsort
是O(n log n)
,而对于单次遍历算法是O(n)
),在足够大的n = p.size
(在我的机器上阈值约为70万)后,单次遍历算法将始终更快。
然而,有一种不那么直接的方法可以使用np.put
对上述for
循环进行向量化:
def np_put(p):
n = p.size
s = np.zeros(n, dtype = np.int32)
i = np.arange(n, dtype = np.int32)
np.put(s, p, i)
return s
对于n = 700,000
(与上面相同的大小),会得到:
p = create_input(700000)
%timeit np_put(p)
100 loops, best of 3: 12.8 ms per loop
这几乎是零成本的5.6倍速提升!
公正地说,对于较小的n
(在我的机器上约为n = 1210
),np.argsort
仍然比np.put
方法更快:
p = create_input(1210)
%timeit np_argsort(p)
10000 loops, best of 3: 25.1 µs per loop
%timeit np_fancy(p)
10000 loops, best of 3: 118 µs per loop
%timeit np_put(p)
10000 loops, best of 3: 25 µs per loop
这很可能是因为我们在
np.arange()
调用时,使用了
np_put
方法分配并填充了一个额外的数组。
虽然您没有要求Cython解决方案,但出于好奇,我也测试了以下使用
类型化内存视图的Cython解决方案的时间。
import numpy as np
cimport numpy as np
def in_cython(np.ndarray[np.int32_t] p):
cdef int i
cdef int[:] pmv
cdef int[:] smv
pmv = p
s = np.empty(p.size, dtype=np.int32)
smv = s
for i in xrange(p.size):
smv[pmv[i]] = i
return s
时间:
p = create_input(700000)
%timeit in_cython(p)
100 loops, best of 3: 2.59 ms per loop
因此,
np.put
的解决方案仍然不够快(输入大小为12.8毫秒; argsort花费了72.7毫秒)。
2017年2月3日更新-使用NumPy 1.11
Jamie、Andris和Paul在下面的评论中指出了使用fancy indexing的性能问题已得到解决。Jamie表示该问题已在NumPy 1.9中得到解决。我在2014年使用的机器上测试了Python 3.5和NumPy 1.11。
def invert_permutation(p):
s = np.empty(p.size, p.dtype)
s[p] = np.arange(p.size)
return s
时间:
p = create_input(880)
%timeit np_argsort(p)
100000 loops, best of 3: 11.6 µs per loop
%timeit invert_permutation(p)
100000 loops, best of 3: 11.5 µs per loop
确实是一项重大改进!
结论
总的来说,我会选择在代码清晰度方面提到的简短回答方法。在我看来,它比argsort
更不明显,并且对于大输入大小而言速度更快。如果速度成为问题,我会选择Cython解决方案。
a = np.array([1, 1, 1, 1])
应该返回什么? - eumiro