Numpy 高效地找到使数组等于自身置换的索引

Numpy 高效地找到使数组等于自身置换的索引,numpy,permutation,Numpy,Permutation,我正在寻找一些函数,它们可以找到使数组等于自身置换的索引 假设p1是不包含重复项的1d Numpy数组。假设p2是p1的排列(重新排序) 我想要一个函数find_position_in_original,使得p2[find_position_in_original(p2,p1)]与p1相同 例如: p1 = np.array(['a', 'e', 'c', 'f']) p2 = np.array(['e', 'f', 'a', 'c']) 其中,find\u position\u in\u排列

我正在寻找一些函数,它们可以找到使数组等于自身置换的索引

假设
p1
是不包含重复项的1d Numpy数组。假设
p2
p1
的排列(重新排序)

我想要一个函数
find_position_in_original
,使得
p2[find_position_in_original(p2,p1)]
p1
相同

例如:

p1 = np.array(['a', 'e', 'c', 'f'])
p2 = np.array(['e', 'f', 'a', 'c'])
其中,
find\u position\u in\u排列(p1,p2)
应返回:

[2, 0, 1, 3]
因为
p2[[2,0,1,3]]
p1
相同

您可以使用列表以暴力方式执行此操作:

def find_position_in_permutation(original, permutation):
    original = list(original)
    permutation = list(permutation)
    return list(map(permutation.index, original))
但我想知道是否有更有效的算法。这个似乎是
O(N^2)


当前答案的基准:

import numpy as np
from string import ascii_lowercase

n = 100

letters = np.array([*ascii_lowercase])
p1 = np.random.choice(letters, size=n)
p2 = np.random.permutation(p1)
p1l = p1.tolist()
p2l = p2.tolist()

def find_pos_in_perm_1(original, permutation):
    """ My original solution """
    return list(map(permutation.index, original))

def find_pos_in_perm_2(original, permutation):
    """ Eric Postpischil's solution, using a dict as a lookup table """
    tbl = {val: ix for ix, val in enumerate(permutation)}
    return [tbl[val] for val in original]

def find_pos_in_perm_3(original, permutation):
    """ Paul Panzer's solution, using an array as a lookup table """
    original_argsort = np.argsort(original)
    permutation_argsort = np.argsort(permutation)
    tbl = np.empty_like(original_argsort)
    tbl[original_argsort] = permutation_argsort
    return tbl

%timeit find_pos_in_perm_1(p1l, p2l)
# 40.5 µs ± 1.13 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%timeit find_pos_in_perm_2(p1l, p2l)
# 10 µs ± 171 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit find_pos_in_perm_3(p1, p2)
# 6.38 µs ± 157 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
您可以使用argsort执行O(N log N):

>>> import numpy as np
>>> from string import ascii_lowercase
>>> 
>>> letters = np.array([*ascii_lowercase])
>>> p1, p2 = map(np.random.permutation, 2*(letters,))
>>> 
>>> o1, o2 = map(np.argsort, (p1, p2))
>>> o12, o21 = map(np.empty_like, (o1, o2))
>>> o12[o1], o21[o2] = o2, o1
>>> 
>>> print(np.all(p1[o21] == p2))
True
>>> print(np.all(p2[o12] == p1))
True
使用Python字典的O(N)解决方案:

>>> import operator as op
>>>    
>>> l1, l2 = map(op.methodcaller('tolist'), (p1, p2))
>>> 
>>> s12 = op.itemgetter(*l1)({k: v for v, k in enumerate(l2)})
>>> print(np.all(s12 == o12))
True
一些时间安排:

26 elements
argsort      0.004 ms
dict         0.003 ms
676 elements
argsort      0.096 ms
dict         0.075 ms
17576 elements
argsort      4.366 ms
dict         2.915 ms
456976 elements
argsort    191.376 ms
dict       230.459 ms
基准代码:

import numpy as np
from string import ascii_lowercase
import operator as op
from timeit import timeit

L1 = np.array([*ascii_lowercase], object)
L2 = np.add.outer(L1, L1).ravel()
L3 = np.add.outer(L2, L1).ravel()
L4 = np.add.outer(L2, L2).ravel()
letters = (*map(op.methodcaller('astype', str), (L1, L2, L3, L4)),)

def use_argsort(p1, p2):
    o1, o2 = map(np.argsort, (p1, p2))
    o12 = np.empty_like(o1)
    o12[o1] = o2
    return o12

def use_dict(l1, l2):
    return op.itemgetter(*l1)({k: v for v, k in enumerate(l2)})

for L, N in zip(letters, (1000, 1000, 200, 4)):
    print(f'{len(L)} elements')
    p1, p2 = map(np.random.permutation, (L, L))
    l1, l2 = map(op.methodcaller('tolist'), (p1, p2))
    T = (timeit(lambda: f(i1, i2), number=N)*1000/N for f, i1, i2 in (
        (use_argsort, p1, p2), (use_dict, l1, l2)))
    for m, t in zip(('argsort', 'dict   '), T):
        print(m, f'{t:10.3f} ms')

听起来像是搜索问题。那不是努比的强项。我对努比不熟悉。我认为这些数组可以用于常规Python函数,而不仅仅是通过numpy。如果是这样,O(nlogn)解决方案是为一个数组中的每个元素在平衡树中插入一个有序对(值、索引),然后在树中查找另一个数组中的每个元素。Python的字典类型可以达到这个目的,尽管我不知道它的底层实现是什么。(可以是一棵树,可以是一个哈希表,也可以是其他可以满足您的性能需要的东西。)如果Python没有提供适合这种情况的内置类型,另一种解决方案是将每个数组映射到(值、索引)列表,然后对每个列表进行排序。然后,两个排序中的索引表示对它们进行排序的排列。将一种排列组合成另一种排列的倒数将提供您所寻求的排列。这是两个O(n logn)排序和一个O(n)组合,所以是O(n logn)。@EricPostFischil是的,它们可以像我的暴力实现一样转换为常规列表。您的第一个解决方案应该使用字典。在小列表中,这比我的原始解决方案和Eric Postdischil的建议都要快。@shadowtalker添加了我自己的基准测试
dict
方法看起来很有竞争力。请注意,使用
operator.itemgetter
进行大容量字典查找似乎比列表理解速度要快得多。