确定两个列表/数组的混洗索引

时间:2017-08-23 04:48:57

标签: python arrays list numpy indexing

作为一项挑战,我已经给自己解决了这个问题:

给出2个列表A和B,其中B是A的混乱版本,其目的是找出混洗的索引。

例如:

A = [10, 40, 30, 2]
B = [30, 2, 10, 40]

result = [2,   3,    0,      1] 
        A[2]  A[3]   A[0]  A[1]
        ||     ||     ||    ||
        30      2     10    40

请注意,可以任意解析相同元素的关系。

我提出了solution,其中涉及使用字典来存储索引。这个问题有什么其他可能的解决方案?使用库的解决方案也有效。 Numpy,pandas,一切都很好。

6 个答案:

答案 0 :(得分:8)

我们可以使用np.searchsorted及其可选的sorter参数 -

sidx = np.argsort(B)
out = sidx[np.searchsorted(B,A, sorter=sidx)]

示例运行 -

In [19]: A = [10, 40, 30, 2, 40]
    ...: B = [30, 2, 10, 40]
    ...: 

In [20]: sidx = np.argsort(B)

In [21]: sidx[np.searchsorted(B,A, sorter=sidx)]
Out[21]: array([2, 3, 0, 1, 3])

答案 1 :(得分:3)

LOL

pd.Series(A).reset_index().set_index(0).ix[B].T.values[0]
#array([2, 3, 0, 1])

答案 2 :(得分:3)

作为对当前解决方案的改进,您可以使用collections.defaultdict并避免dict.setdefault

from collections import defaultdict

A = [10, 40, 30, 2]
B = [30, 2, 10, 40]

idx = defaultdict(list)
for i, l in enumerate(A):
    idx[l].append(i)

res = [idx[l].pop() for l in B]
print(res)

以下是使用给定样本输入的两种方法的时间安排:

用于测试的脚本

from timeit import timeit


setup = """
from collections import defaultdict;
idx1 = defaultdict(list); idx2 = {}
A = [10, 40, 30, 2]
B = [30, 2, 10, 40]
"""

me = """
for i, l in enumerate(A):
    idx1[l].append(i)
res = [idx1[l].pop() for l in B]
"""

coldspeed = """
for i, l in enumerate(A):
    idx2.setdefault(l, []).append(i)
res = [idx2[l].pop() for l in B]
"""

print(timeit(setup=setup, stmt=me))
print(timeit(setup=setup, stmt=coldspeed))

<强>结果

original: 2.601998388010543
modified: 2.0607256239745766

所以似乎使用defaultdict实际上会产生轻微的速度提升。这实际上是因为虽然defaultdict是用C而不是Python实现的。更不用说原始解决方案的属性查找 - idx.setdefault1 - 代价很高。

答案 3 :(得分:2)

正如我在问题中所提到的,我能够使用字典来解决这个问题。我将索引存储在dict中,然后使用列表推导将其弹出:

A = [10, 40, 30, 2]
B = [30, 2, 10, 40]

idx = {}
for i, l in enumerate(A):
    idx.setdefault(l, []).append(i)

res = [idx[l].pop() for l in B]
print(res)

输出:

[2, 3, 0, 1]

这比显而易见的[A.index(x) for x in B]要好,因为它是

  1. 线性
  2. 优雅地处理重复

答案 4 :(得分:2)

numpy_indexed包有一个有效且通用的解决方案:

import numpy_indexed as npi
result = npi.indices(A, B)

请注意,它有一个kwarg来设置处理缺失值的模式;它适用于任何类型的nd数组,就像使用1d整数数组一样。

答案 5 :(得分:1)

由于发布了几个非常好的解决方案,我冒昧地组装了一些粗略的时间来比较每种方法。

用于测试的脚本

from timeit import timeit


setup = """
from collections import defaultdict
import pandas as pd 
import numpy as np 
idx1 = defaultdict(list); idx2 = {}
A = [10, 40, 30, 2]
B = [30, 2, 10, 40]
"""

me = """
for i, l in enumerate(A):
    idx1[l].append(i)
res = [idx1[l].pop() for l in B]
"""

coldspeed = """
for i, l in enumerate(A):
    idx2.setdefault(l, []).append(i)
res = [idx2[l].pop() for l in B]
"""

divakar = """
sidx = np.argsort(B)
res = sidx[np.searchsorted(B,A, sorter=sidx)]
"""

dyz = """
res = pd.Series(A).reset_index().set_index(0).ix[B].T.values[0]
"""

print('mine:', timeit(setup=setup, stmt=me, number=1000))
print('coldspeed:', timeit(setup=setup, stmt=coldspeed, number=1000))
print('divakar:', timeit(setup=setup, stmt=divakar, number=1000))
print('dyz:', timeit(setup=setup, stmt=dyz, number=1000))

结果/输出(在Jupyter笔记本服务器上运行.1000循环)

mine: 0.0026700650341808796
coldspeed: 0.0029303128831088543
divakar: 0.02583012101240456
dyz: 2.208147854078561

以下是一些时间,A的大小是100,000个随机数。 B是它的混乱等价物。该程序太耗费时间和内存消耗。此外,我将循环次数减少到100.否则,一切都与上面相同:

mine: 17.663535300991498
coldspeed: 17.11006522300886
divakar: 8.73397267702967
dyz: 44.61878849985078