两个数组的交集,保留较大数组中的顺序

时间:2017-03-24 00:31:50

标签: python arrays algorithm numpy optimization

我有一个长度为a的numpy数组n,其数字0到n-1以某种方式改组。我还有一个长度为< = mask的numpy数组n,其中包含a元素的某些子集,顺序不同。

我要计算的查询是"根据它们出现在" a提供mask的元素>。

我有一个类似的问题here,但区别在于mask是一个布尔掩码而不是单个元素上的掩码。

我已经概述并测试了以下4种方法:

import timeit
import numpy as np
import matplotlib.pyplot as plt

n_test = 100
n_coverages = 10

np.random.seed(0)


def method1():
    return np.array([x for x in a if x in mask])


def method2():
    s = set(mask)
    return np.array([x for x in a if x in s])


def method3():
    return a[np.in1d(a, mask, assume_unique=True)]


def method4():
    bmask = np.full((n_samples,), False)
    bmask[mask] = True
    return a[bmask[a]]


methods = [
    ('naive membership', method1),
    ('python set', method2),
    ('in1d', method3),
    ('binary mask', method4)
]

p_space = np.linspace(0, 1, n_coverages)
for n_samples in [1000]:
    a = np.arange(n_samples)
    np.random.shuffle(a)

    for label, method in methods:
        if method == method1 and n_samples == 10000:
            continue
        times = []
        for coverage in p_space:
            mask = np.random.choice(a, size=int(n_samples * coverage), replace=False)
            time = timeit.timeit(method, number=n_test)
            times.append(time * 1e3)
        plt.plot(p_space, times, label=label)
    plt.xlabel(r'Coverage ($\frac{|\mathrm{mask}|}{|\mathrm{a}|}$)')
    plt.ylabel('Time (ms)')
    plt.title('Comparison of 1-D Intersection Methods for $n = {}$ samples'.format(n_samples))
    plt.legend()
    plt.show()

产生了以下结果:

enter image description here

因此,毫无疑问,二元掩模对于任何尺寸的掩模来说都是这4的最快方法。

我的问题是,有更快的方法吗?

2 个答案:

答案 0 :(得分:2)

  

因此,毫无疑问,二元掩模对于任何尺寸的掩模来说都是这4的最快方法。

     

我的问题是,有更快的方法吗?

我完全同意二元掩码方法是最快的。我也不认为在计算复杂性方面可以有更好的方法来做你需要的。

让我分析您的方法时间结果:

  1. 方法运行时间为 T = O(| a | * | mask |)时间。通过遍历其每个元素,检查 a 的每个元素是否存在于 mask 中。在掩码中缺少元素的最坏情况下,它为每个元素提供 O(| mask |)时间。 | a | 不会改变, 认为它是一个常数。
    |掩模| = coverage * | a |
    T = O(| a | 2 *覆盖率)
    因此, coverage 在图中的线性依赖性。请注意,运行时具有 | a | 二次依赖关系。如果 |掩码| ≤| a | | a | = n 然后 T = O(n 2

  2. 第二种方法是使用设置。 Set是一个数据结构,在 O(log(n))中执行插入/查找操作,其中 n 是集合中的多个元素。 s = set(mask)需要 O(| mask | * log(| mask |))才能完成,因为有 | mask | 插入操作。

    x in s是一个查找操作。所以第二行在 O(| a | * log(| mask |))

    中运行

    总体时间复杂度为 O(| mask | * log(| mask |)+ | a | * log(| mask |))。如果 |掩码| ≤| a | | a | = n 然后 T = O(n * log(n)) 。您可能会观察 f(x)= log(x)对绘图的依赖。

  3. in1d 也在 O(| mask | * log(| mask |)+ | a | * log(| mask |))中运行。相同的 T = O(n * log(n)) 复杂度和 f(x)= log(x)对绘图的依赖性。

  4. 时间复杂度为 O(| a | + | mask |) T = O(n) 及其最好的。您在绘图上观察到常量依赖性。算法只需迭代遍历 a 掩码数组。

  5. 问题在于,如果您必须输出 n 项,则您已经具有 T = O(n) 复杂度。所以这种方法4算法是最优的。

    P.S。为了观察上面提到的 f(n)依赖关系,你最好改变 | a | 并让 | mask | = 0.9 * | a |

    编辑:看起来python集确实使用哈希表在O(1)中执行查找/插入。

答案 1 :(得分:0)

假设a是更大的那个。

def with_searchsorted(a, b):

    sb = b.argsort()
    bs = b[sb]

    sa = a.argsort()
    ia = np.arange(len(a))
    ra = np.empty_like(sa)
    ra[sa] = ia

    ac = bs.searchsorted(ia) % b.size

    return a[(bs[ac] == ia)[ra]]

演示

a = np.arange(10)
np.random.shuffle(a)
b = np.random.choice(a, 5, False)

print(a)
print(b)

[7 2 9 3 0 4 8 5 6 1]
[0 8 5 4 6]

print(with_searchsorted(a, b))

[0 4 8 5 6]

如何运作

# sort b for faster searchsorting
sb = b.argsort()
bs = b[sb]

# sort a for faster searchsorting
sa = a.argsort()
# this is the sorted a... we just cheat because we know what it will be
ia = np.arange(len(a))

# construct the reverse sort look up
ra = np.empty_like(sa)
ra[sa] = ia

# perform searchsort
ac = bs.searchsorted(ia) % b.size

return a[(bs[ac] == ia)[ra]]