首先,进行一些转换:
positions2 :: (Eq a) => a -> [a] -> [Int]
positions2 = f where
f aa aas = filter (g aa aas) [0 .. (length aas - 1)]
g aa aas it = aa == aas !! it
positions2 aa aas = filter (g aa aas) [0 .. (length aas - 1)]
g aa aas it = aa == aas !! it
positions2 aa aas = [it | it <- [0 .. (length aas - 1)], aa == (aas !! it)]
现在你可以清楚地看到,对于给定的
it
,通过
(!!)
遍历列表
aas
是从其起始位置重新开始的,这是一种典型的二次行为,当完全探索
positions2 aa aas
的结果时(返回的列表被遍历到其末尾),您执行了
1+2+3+4+...+n = O(n2)步骤。
但是,您正在探索逐渐增加的索引,因此您可以从列表中的前一个点(而不是从其开头)开始,并且每次仅向前移动1个位置(而不是像
(!!)
那样移动整个
it
索引)。
positions2 aa aas = g 0 aas
where
g i (a:as) | a == aa = i : g (i+1) as
| otherwise = g (i+1) as
g _ [] = []
在这里,您可以看到索引加1(
i-->i+1
)与沿着列表向前移动一个位置(
(a:as)-->as
)的增量是如何交织在一起的。
再次使用列表推导式,通过使用
zip
来实现编织(或实际上更像是压缩)。
positions2 aa aas = [it | (a,it) <- zip aas [0 .. (length aas - 1)]
, a == aa]
这现在显然是一个O(n)的解决方案。由于Haskell的列表是惰性的,而
zip
会在最短的列表结束时停止。
positions2 aa aas = [it | (a,it) <- zip aas [0 ..], a == aa]
(这相当于
这里的答案 中的代码。)