查找具有给定排名的所有固定长度子数组
Posted
技术标签:
【中文标题】查找具有给定排名的所有固定长度子数组【英文标题】:Find all subarrays of fixed length with a given ranking 【发布时间】:2019-05-31 22:28:15 【问题描述】:我有一个数字数组,例如:
A = [1, 5, 2, 4, 3]
还有一个决定排名的数组,例如:
B = [0, 2, 1]
我的目标是找到 A 的所有“服从”等级 B 的子数组。如果子数组服从等级,这意味着子数组的第 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)
更好)来完成这项任务。
【问题讨论】:
您是否正在寻找时间复杂度较低的东西?因为我不确定这样的事情是否存在。 @ParitoshSingh 是的,这就是我要找的。也许不是,但我想这就是我问的原因:)。让我怀疑的是我正在处理子数组,其中一些子数组重叠 - 也许有一种方法可以通过缓存一些来减少计算量,比如优化字符串搜索(如 KMP)算法的工作原理?跨度> 我看到的问题是这个。考虑 [0,1,3,2]。在第一个子列表中,[1,3] 的相对等级为 1 和 2,而在第二个子列表中,[1,3] 的相对等级为 0 和 2。本质上,结果取决于“窗口” ,因此您需要重新评估才能确定。在这种情况下,您可能缓存的任何结果最终都需要重新检查,从而失去所有好处。 (如果我错了,请有人纠正我) @ParitoshSingh 正确,但是考虑长度 > 2 的子数组。例如,当我从 [0, 1, 3] 转到 [1, 3, 2] 时(在您的列表中) ,假设我对第一个子数组进行了比较并推断它不服从。我继续第二个子数组,但是我可能已经做了一些比较(最后两个元素成为第二个子数组的前两个元素)。即使,正如您所说,知道 1 确实如此,但是因为它的“一些”案例而不是全部案例,所以无论如何您都必须重新检查所有案例。而且由于比较是一个恒定的时间操作,因此您最终会出现在第 1 格。更改窗口会改变有关比较有利和不利的所有内容。 【参考方案1】:您可以使用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 的原因。
【讨论】:
谢谢,但是我对使用的算法更感兴趣,并且我查看了 scipy 源 - 如果我错了,请纠正我 - 但看起来他们正在对列表进行排序 -所以最终复杂度并不比 O(mn) 好? @pingguo 是的,它看起来使用了来自source code 的合并排序或快速排序。在那种情况下,上面的可能会更慢,因为它需要为每个排名执行 O(nlogn) 排序。您必须同时确定两者的时间。我认为您无法比现有的解决方案做得更好。【参考方案2】:这是基于一些线性代数的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 解决方案的表现会更好:
这是您的代码,我将其转换为返回所需子数组(而不是打印)的函数:
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
【讨论】:
感谢您的回答 - 我从来没有想过这样做!然而,虽然这提高了速度,但在我看来它仍然像O(nm)
,因为点积是 O(n)
:(。是的,最重要的是,由于中断,大 m 会导致更短的时间。【参考方案3】:
您可以遍历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]]
【讨论】:
你在变量中使用的所有下划线是怎么回事? @coldspeed 纯属个人风格 感谢您的及时答复!但是,也许我的问题不够清楚,我只需要服从排名的子数组 of A。您的解决方案为我提供了所有可能服从排名的子数组,但其中大多数不是 A 的子数组。这不会使此方法更长(因为我必须删除不属于 A 的子数组)? @pingguo 根据计时结果,您似乎真的无法比已有的解决方案更好。 @pault 我添加了新的计时,但是,如果您觉得它们不准确,我会删除它们。【参考方案4】:至少,我们可以通过考虑相邻元素的(二元)关系来更快地排除候选窗口,这可以允许并行检查。致电less than
0
和greater than
1
。那么:
A = [1, 5, 2, 4, 3]
A'= [0, 1, 0, 1]
B'= [0, 1]
B = [0, 2, 1]
显然,任何候选者都必须匹配关系序列。另请注意,B
中唯一可以允许重叠的部分类型是升序或降序(这意味着如果找到匹配项,我们可以先验地跳过)。
【讨论】:
以上是关于查找具有给定排名的所有固定长度子数组的主要内容,如果未能解决你的问题,请参考以下文章