元组生成中的对称性破坏
我的印象是您正在寻找一种从列表中选择两个元素的方法,这样x1 总是位于之前 x2。
始终让x2 遍历列表其余部分的常用方法是使用tails :: [a] -> [[a]]。对于列表,tails 将生成列表所有尾部的列表,从列表本身开始。例如:
Prelude Data.List> tails [1, 4, 2, 5]
[[1,4,2,5],[4,2,5],[2,5],[5],[]]
我们可以使用它与模式匹配来选择一个元素,并获得对剩余元素的引用。例如:
import Data.List(tails)
tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = [(x1,x2) | (x1:x2s) <- tails xs, x2 <- x2s, x1 + x2 == n]
注意这里仍然可以得到重复,例如5会在列表中出现3次,因为在这种情况下x1可以选择第一个5,然后x2可以选择第二个5 以及最后一个。为此,我们可以使用像 nub :: Eq a => [a] -> [a] 这样的 uniqness 过滤器:
import Data.List(nub, tails)
tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = nub [(x1,x2) | (x1:x2s) <- tails xs, x2 <- x2s, x1 + x2 == n]
请注意,这里最好使用tails,因为它会提高性能,因为我们首先会生成少量的重复项。
使用哈希集获取“其他”元素
上面的算法还是O(n2),而且不是很快。然而我们可以反过来解决这个问题:我们可以先构造一个元素的HashSet,然后对于每个元素x1,检查n - x1是否是一个成员,比如:
import Data.Hashable(Hashable)
import Data.HashSet(fromList, member)
tuplesum :: (Ord b, Hashable b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = nub [(x1,x2) | x1 <- xs, let x2 = n-x1, x1 <= x2, member x2 hs]
where hs = fromList xs
但是由于nub,运行时间仍然是O(n2),但是我们可以在这里使用hashNub :: (Eq a, Hashable a) => [a] -> [a]:
hashNub :: (Eq a, Hashable a) => [a] -> [a]
hashNub = go HashSet.empty
where
go _ [] = []
go s (x:xs) =
if x `HashSet.member` s
then go s xs
else x : go (HashSet.insert x s) xs
然后让它工作:
import Data.Hashable(Hashable)
import Data.HashSet(fromList, member)
tuplesum :: (Ord b, Hashable b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = hashNub [(x1,x2) | x1 <- xs, let x2 = n-x1, x1 <= x2, member x2 hs]
where hs = fromList xs
现在它可以在 O(n log n) 中运行。