【问题标题】:Efficiently find indexes that would make array equal to a permutation of itself有效地找到使数组等于自身排列的索引
【发布时间】:2018-10-04 01:44:54
【问题描述】:

我正在寻找一些函数,它可以找到使数组等于自身排列的索引。

假设p1 是一个不包含重复项的一维 Numpy 数组。假设p2p1 的排列(重新排序)。

我想要一个函数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_position_in_permutation(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)

【问题讨论】:

  • 听起来像是搜索问题。这不是一种麻木的力量。
  • 我对numpy不熟悉。我认为这些数组可用于常规 Python 函数,而不仅仅是通过 numpy.如果是这样,一个 O(n log n) 的解决方案是为一个数组中的每个元素插入一个有序对(值,索引)到平衡树中,然后在树中查找另一个数组的每个元素。 Python 的字典类型可以达到这个目的,虽然我不知道它的底层实现是什么。 (可以是一棵树,可以是一个哈希表,也可以是其他东西——无论它是什么,都可以满足您的性能需求。)
  • 如果 Python 没有提供适合这种情况的内置类型,另一种解决方案是将每个数组映射到 (value, index) 的列表,然后对每个列表进行排序。两个排序后的索引表示对它们进行排序的排列。将一个排列与另一个排列组合将提供您寻求的排列。也就是两个 O(n log n) 排序和一个 O(n) 组合,所以它是 O(n log n)。
  • @EricPostpischil 是的,它们可以像我的蛮力实现一样转换为常规列表。您的第一个解决方案应该使用字典。

标签: numpy permutation


【解决方案1】:

您可以使用 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')

【讨论】:

  • 在小型列表中,这比我原来的解决方案和 Eric Postpischil 的提议都要快。
  • @shadowtalker 添加了我自己的基准。 dict 方法看起来很有竞争力。请注意,使用operator.itemgetter 进行批量字典查找似乎大大加快了列表理解的速度。
猜你喜欢
  • 2012-03-05
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-24
  • 1970-01-01
  • 1970-01-01
  • 2023-02-21
相关资源
最近更新 更多