Python 查找具有给定排名的固定长度的所有子阵列

Python 查找具有给定排名的固定长度的所有子阵列,python,algorithm,ranking,Python,Algorithm,Ranking,我有一个数字数组,例如: A = [1, 5, 2, 4, 3] B = [0, 2, 1] 以及确定秩的数组,例如: A = [1, 5, 2, 4, 3] B = [0, 2, 1] 我的目标是找到“服从”秩B的A的所有子数组。如果子数组服从秩,这意味着子数组的第i个最小元素必须具有B[i]作为其(子数组)索引。因此,子阵列要匹配,其中最小的元素必须在位置0,第二个最小的元素必须在位置2,最大的元素必须在位置1 例如,这里有两个A的子数组与排名匹配:[1,5,2](因为A[0]

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

A = [1, 5, 2, 4, 3]
B = [0, 2, 1]
以及确定秩的数组,例如:

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)
更好)来完成这项任务。

您可以循环
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)))

您可以使用直接获取列组,而不是在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

这是一个基于一些线性代数的
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]
我不是numpy专家,因此可能有一种方法可以进一步优化此功能并消除循环


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

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解决方案的性能会更好:

以下是我将其转换为返回所需子数组(而不是打印)的函数的代码:

大型随机
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

至少,通过考虑相邻元素的(二进制)关系,我们可以更快地排除候选窗口,从而允许并行检查。调用
小于
0
大于
1
。然后:

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

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

显然,任何候选者都必须匹配关系序列。还请注意,
B
中唯一允许重叠的部分类型是升序或降序(这意味着如果找到匹配项,我们可能会提前跳过)。

变量中使用的所有下划线是怎么回事?@coldspeed只是个人风格感谢您的快速回答!然而,也许我在我的问题上不够清楚,我只需要服从排名的A的子数组。你的解决方案给了我所有可能的服从排名的子数组,但其中大多数不是A的子数组。这难道不会使这个方法更长(因为我必须删除不属于A的子数组)?@平果根据计时结果,看来,对于解决方案,您真的无法获得比现有解决方案更好的解决方案。@pault我添加了新的计时,但是,如果您觉得它们不准确,我将删除它们。您是在寻找时间复杂度更低的解决方案吗?因为我不确定这样的东西是否存在。@Paritossingh是的,这就是我要找的。也许没有,但我想这就是为什么我问:)。但让我怀疑的是,我正在研究子阵列,其中一些子阵列重叠-也许有一种方法可以通过缓存一些子阵列来减少计算量,比如优化字符串搜索(如KMP)算法的工作方式?我看到的问题是这一点。考虑[0,1,3,2]。在第一个子列表中,[1,3]的相对秩为1和2,而在第二个子列表中,[1,3]的相对秩为0和2。本质上,结果取决于“窗口”,因此需要重新评估才能确定。在这种情况下,无论缓存的结果是什么,最终都需要重新检查,失去了所有好处。(如果我错了,有人请纠正我)“PrimoSothigh”是正确的,但是考虑长度为2的子数组。例如,当我从[0,1,3]到[1,3,2](在您的列表中)时,假设我对第一个子阵列进行了比较,并推断它不符合要求。我转到第二个子阵列,但是我可能已经做了一些比较(最后两个元素成为第二个子阵列的前两个元素)。尽管正如你所说的,知道1<3是不有用的,因为2是中间的,但是有些情况下,子数组的重叠部分必须是有用的——至少,这就是我所想的。确实,但是因为它的“一些”情况而不是全部,所以无论如何都必须重新检查所有的情况。由于比较是一个恒定时间的运算,所以最终会得到平方1。更改窗口会改变关于比较的一切,有利的和不利的。谢谢,但是我对使用的算法更感兴趣,我已经查看了scipy源代码-如果我错了,请纠正我-但是看起来他们正在对列表排序-所以最后复杂性并不比O(mn)好?@平果是的,它看起来像是使用了合并排序或快速排序。在这种情况下,由于需要对每个排名执行O(nlogn)排序,上述操作可能会较慢。你必须两个都计时才能确定。我不认为你能做得比你的解决方案更好。谢谢你的回答-我从来没有想过这样做!然而,尽管