我对Haskell标准库 Data.List 中'nub'(选择唯一值)函数的实现感到困惑。 GHC的实施是
nub l = nub' l []
where
nub' [] _ = []
nub' (x:xs) ls
| x `elem` ls = nub' xs ls
| otherwise = x : nub' xs (x:ls)
据我所知,这有一个最坏情况下的时间复杂度为O(n ^ 2),因为对于一个唯一值列表,它必须比较它们一次才能看到它们实际上是唯一的。 / p>
如果使用哈希表,则复杂性可以减少到O(n)以构建表+ O(1)以检查每个值与哈希表中的先前值。当然,这不会产生有序列表,但如果有必要,也可以在O(n log n)中使用GHC自己的有序Data.Map。
为什么为重要的库函数选择这样低效的实现?我知道效率不是Haskell的主要关注点,但至少标准库可以努力为工作选择(渐近)最佳数据结构。
答案 0 :(得分:10)
你是绝对正确的 - nub
是一个O(n ^ 2)算法。但是,仍然有理由要使用它而不是使用hashmap:
nub
只需要Eq
约束;相比之下,Data.Map
要求对密钥设置Ord
约束,Data.HashMap
要求密钥类型同时包含Hashable
和Ord
类型编辑:对第三点进行轻微修正 - 您无需处理整个列表即可开始获取结果;你仍然需要检查输入列表的每个元素(因此nub
不能在无限列表上工作),但是一旦找到一个唯一的元素,你就会开始返回结果。
答案 1 :(得分:9)
在Haskell中,效率是一个非常值得关注的问题,毕竟语言与Java相当,并且在内存消耗方面胜过它,但当然不是C。
您的问题的答案非常简单:Prelude的nub
仅需要Eq
约束,而基于Map
或Set
的任何实施也需要Ord
或Hashable
。
答案 2 :(得分:4)
https://groups.google.com/forum/m/#!msg/haskell-cafe/4UJBbwVEacg/ieMzlWHUT_IJ
根据我的经验,“初学者”Haskell(包括Prelude和坏包)在很多情况下都会忽略性能,而不是简单。
Haskell性能是一个需要解决的复杂问题,因此如果您没有足够的经验来搜索平台或Hackage以寻找简单nub
的替代方法(特别是如果您的输入位于List中仅仅是因为您没有不考虑替代结构),那么Data.List.nub
可能不是你唯一的主要性能问题,而且你可能也在为性能无关紧要的玩具项目编写代码。
您必须相信,当您构建大型(代码或数据)项目时,您将更有经验并且知道如何更有效地设置程序。
换句话说,不要担心它,并假设来自Prelude或base的Haskell 98中的任何内容可能不是解决问题的最有效方法。