由于一个代码示例胜过千言万语,我将从那开始:
testList = [1,2,2,3,4,5]
testSet = map sumMapper $ tails testList
where sumMapper [] = []
sumMapper (a:b) = sumMap a b
sumMap a b = map (+ a) b
此代码采用一个列表并将所有元素相加以获得所有元素的总和(我也对此效率感兴趣)。 testSet的输出是:
[[3,3,4,5,6],[4,5,6,7],[5,6,7],[7,8],[9],[],[]]
我想找到这些列表的联合(使其成为一组)但我觉得:
whatIWant = foldl1 union testSet
会有糟糕的表现(真正的名单会长达数千个元素)。
这是正确的解决方案还是我遗漏了一些明显的东西?
答案 0 :(得分:5)
如果您使用属于Ord
类型类的成员的元素(如示例中所示),则可以使用Data.Set
:
import qualified Data.Set as Set
whatYouWant = foldl' (Set.union . Set.fromList) Set.empty testSet
与Set.fromList . concat
解决方案一样,这样做的优点是可以使空间与最大子列表的大小成比例,而不是与整个连接列表的大小成比例。 strict foldl'
还可以防止未评估的thunk堆积,防止O(n)
堆栈和堆空间使用。
一般来说,Ord
约束允许比Eq
约束更有效的算法,因为它允许您构建树。这也是nub
O(n^2)
的原因:效率更高的算法需要Ord
,而不仅仅是Eq
。
答案 1 :(得分:4)
您可能想尝试
nub $ concat theListOfLists
在使用union
的版本中,删除重复项的代码将多次运行。这里只运行一次。
它只执行代码以提取一次唯一值。
还有一个Data.Set库,您也可以使用
import Data.Set
S.fromList $ concat theListOfLists
重要的一点是,提取重复项的代码(此处和上方)只能在完整列表中运行一次,而不是一遍又一遍。
编辑 - 在下面提到nub是O(n ^ 2),所以你应该避免上面的第一个解决方案支持O(n log n),因为Data.Set.fromList应该是。正如其他人在评论中提到的那样,你需要一些能够强制Ord a
来获得适当复杂性O(n log n)
的东西,而Data.Set则需要强制实现复制{{1}}。
我会留下两个解决方案(性能不佳和性能良好),因为我认为最终的讨论很有用。
答案 2 :(得分:1)
由于union
是关联操作( a +(b + c)==(a + b)+ c ),因此您可以使用树形折叠来获得对数优势时间复杂度:
_U [] = []
_U (xs:t) = union xs (_U (pairs t))
pairs (xs:ys:t) = union xs ys : pairs t
pairs t = t
当然Data.List.union
本身就是 O(n 2 ),但是如果你的testList
被命令为非递减,则所有列表也是如此,您可以使用线性ordUnion
代替union
,以获得整体线性的解决方案,不应泄漏空间:
ordUnion :: (Ord a) => [a] -> [a] -> [a]
ordUnion a [] = a
ordUnion [] b = b
ordUnion (x:xs) (y:ys) = case compare x y of
LT -> x : ordUnion xs (y:ys)
EQ -> x : ordUnion xs ys
GT -> y : ordUnion (x:xs) ys
为了防止可能漏掉的重复项,需要另外一个函数来处理_U
的输出 - 线性ordNub :: (Ord a) => [a] -> [a]
,具有明显的实现。
使用左优先(\(x:xs) ys -> x:ordUnion xs ys)
可以提高整体效率(在每个给定时刻强制输入较小的部分):
g testList = ordNub . _U $ [map (+ a) b | (a:b) <- tails testList]
where
_U [] = []
_U ((x:xs):t) = x : ordUnion xs (_U (pairs t))
pairs ((x:xs):ys:t) = (x : ordUnion xs ys) : pairs t
pairs t = t
另见: