查找具有给定排名的固定长度的所有子数组

时间:2019-01-05 17:23:21

标签: python algorithm ranking

我有一个数字数组,例如:

A = [1, 5, 2, 4, 3]

和确定排名的数组,例如:

B = [0, 2, 1]

我的目标是找到“服从”等级B的A的所有子阵列。如果一个子阵列服从等级,则意味着该子阵列的第i个最小元素必须以B[i]作为其(子数组)索引。因此,要使子数组匹配,其中的最小元素必须位于位置0,第二个最小元素必须位于位置2,最大元素必须位于位置1。

因此,例如,这里有两个与排名匹配的A子数组:[1、5、2](因为A [0]

到目前为止,我设法找到了一个解决方案,它的时间复杂度为O(mn)(m为len(A),n为len(B)),它遍历了长度为3的所有子数组,验证它们是否正确订购:

A = [1, 5, 2, 4, 3]
B = [0, 2, 1]
m = len(A)
n = len(B)
for i in range(m - n + 1):
    current_subarray = A[i:i + n]
    # we now do n - 1 comparaisons to check whether the subarray is correctly ordered.
    for B_index in range(n - 1):
        if current_subarray[B[B_index]] > current_subarray[B[B_index + 1]]:
            break
    else:
        print("Subarray found:", current_subarray)

结果:

Subarray found: [1, 5, 2]
Subarray found: [2, 4, 3]

这行得通,但是我想知道是否有一种更好的优化算法(优于O(mn))来完成此任务。

4 个答案:

答案 0 :(得分:3)

您可以使用scipy.stats.rankdata直接获取排名,而不是遍历B来比较排名:

from scipy.stats import rankdata

A = [1, 5, 2, 4, 3]
B = [0, 2, 1]

m = len(A)
n = len(B)

for i in range(m - n + 1):
    current_subarray = A[i:i + n]

    ranked_numbers = (rankdata(current_subarray).astype(int) - 1).tolist()
    if ranked_numbers == B:
        print("Subarray found:", current_subarray)

# Subarray found: [1, 5, 2]
# Subarray found: [2, 4, 3]

注意: rankdata()从1而不是0开始排名,这就是上面的代码在numpy数组的每个元素中减去1的原因。

答案 1 :(得分:3)

这是一个基于线性代数的numpy解决方案。

首先将B转换为基础:

import numpy as np
A = [1, 5, 2, 4, 3]
B = [0, 2, 1]

b = np.eye(len(B))[B]
print(b)
#array([[1, 0, 0],
#       [0, 0, 1],
#       [0, 1, 0]])

现在,我们可以遍历A的每个子数组并将其投影到该空间中。如果对所得向量进行排序,则表示子数组紧随其后。

for i in range(0, (len(A) - len(B))+1):
    a = np.array(A[i:i+len(B)])
    if (np.diff(a.dot(b))>0).all():
        print(a)
#[1 5 2]
#[2 4 3]

我不是一个麻木的专家,所以也许有办法进一步优化它并消除循环。


更新,这是一个更干净的版本:

def get_ranked_subarrays(A, B):
    m = len(A)
    n = len(B)
    b = np.eye(n)[B]
    a = np.array([A[i:i+n] for i in range(0, m - n+1)])
    return a[(np.diff(a.dot(b))>0).all(1)].tolist()

A = [1, 5, 2, 4, 3]
B = [0, 2, 1]
get_ranked_subarrays(A, B)
#[[1, 5, 2], [2, 4, 3]]

效果结果:

您的解决方案非常适合小型n,但随着A的大小变大,numpy解决方案的性能会超越:

这是您的代码,我将其变成一个返回所需子数组(而不是打印)的函数:

def get_ranked_subarrays_op(A, B):
    m = len(A)
    n = len(B)
    out = []
    for i in range(m - n + 1):
        current_subarray = A[i:i + n]
        # we now do n - 1 comparisons to check whether the subarray is correctly ordered.
        for B_index in range(n - 1):
            if current_subarray[B[B_index]] > current_subarray[B[B_index + 1]]:
                break
        else:
            out.append(current_subarray)
    return out

大型随机A的计时结果:

array_size = 1000000
A = np.random.randint(low=0, high=10, size=array_size)
B = [0, 2, 1]

%%timeit
get_ranked_subarrays_op(A, B)
#1 loop, best of 3: 1.57 s per loop

%%timeit
get_ranked_subarrays(A, B)
#1 loop, best of 3: 890 ms per loop

但是,如果m也变大,则由于短路(对于大m而言,短路的可能性会变大),因此您的解决方案会稍微好一些。这是我们将m设为100的计时结果。

array_size = 1000000
basis_size = 100
A = np.random.randint(low=0, high=10, size=array_size)
B = range(basis_size)
np.random.shuffle(B)

%%timeit
get_ranked_subarrays_op(A, B)
#1 loop, best of 3: 1.9 s per loop

%%timeit
get_ranked_subarrays(A, B)
#1 loop, best of 3: 2.79 s per loop

答案 2 :(得分:2)

您可以遍历A并检查结果子数组:

A, B = [1, 5, 2, 4, 3], [0, 2, 1]
def results(a, b):
   _l = len(b)
   for c in range(len(a)-_l+1):
     _r = a[c:c+_l]
     new_r = [_r[i] for i in b]
     if all(new_r[i] < new_r[i+1] for i in range(len(new_r)-1)):
       yield _r

print(list(results(A, B)))

输出:

[[1, 5, 2], [2, 4, 3]]

答案 3 :(得分:1)

至少,我们可以通过考虑相邻元素的(二进制)关系来更快地排除候选窗口,这可能允许并行检查。呼叫less than 0greater than 1。然后:

A = [1,  5,  2,  4,  3]
A'=   [0,  1,  0,  1]

B'=   [0,  1]
B = [0,  2,  1]

很明显,任何候选人都必须匹配关系序列。还要注意,B唯一允许重叠的部分类型是升序或降序(意味着如果找到匹配项,我们可以先验跳过)。