Map.Map 与函数
Posted
技术标签:
【中文标题】Map.Map 与函数【英文标题】:Map.Map vs function 【发布时间】:2011-09-16 13:24:19 【问题描述】:为什么在这种情况下使用数据构造函数和函数比使用字符串和映射慢?
编辑:请注意我对 Rotsor 回答的第二条评论,它解释了为什么我接受了 nulvinges 的回答。
运行缓慢:
module Main where
import qualified Data.List as List
main :: IO ()
main = do
print $ conspire fabFive fabFive
-- here i actually have 80 constructors
data Eris = Hera | Athene | Aphrodite | Paris | Helene
deriving (Ord, Eq, Show, Read, Enum, Bounded)
fabFive = [minBound..maxBound] :: [Eris]
conspire :: [Eris] -> [Eris] -> [Eris]
conspire [Hera] [Hera] = [Hera, Athene]
...
conspire [Hera] [Helene] = [Athene, Aphrodite, Paris]
...
conspire [Helene] [Helene] = [Hera]
conspire [a] (b:bs) =
List.union (conspire [a] [b]) (conspire [a] bs)
conspire (a:as) ls =
List.union (conspire [a] ls) (conspire as ls)
跑得更快:
module Main where
import qualified Data.Map as Map
import qualified Data.Set as Set
main :: IO ()
main = do
print $ conspire (Set.fromList fabFive) (Set.fromList fabFive)
fabFive = [ Hera, Athene, Aphrodite, Paris, Helene ]
conspire :: Set.Set String -> Set.Set String -> Set.Set String
conspire set1 set2 = Set.fold Set.union Set.empty $ Set.map
(\x -> Set.fold Set.union Set.empty $ Set.map
(\y -> conspiracy Map.! (Set.singleton x, Set.singleton y))
set2
)
set1
conspiracy = Map.fromList
[ ( (Set.singleton "Hera" , Set.singleton "Hera" )
, Set.fromList [ "Hera", "Athene" ]
)
...
, ( (Set.singleton "Hera" , Set.singleton "Helene" )
, Set.fromList [ "Athene", "Aphrodite", "Paris" ]
)
...
, ( (Set.singleton "Helene" , Set.singleton "Helene" )
, Set.fromList [ "Hera" ]
)
]
【问题讨论】:
【参考方案1】:您的第一个版本运行缓慢,因为List.nub
功能非常低效。它适用于O(N^2)
时间,N
是列表的大小。对于大的N
,其他所有内容都将由nub
主导。
【讨论】:
最初我没有使用 List.nub,而是使用了以下函数定义:composition [a] (b:bs) = List.union (composition [a] [b]) (composition [a] bs)
和 composition (a:as) ls = List.union (composition [a] ls) (composition as ls)
这和 List.nub
一样慢,我曾经在第一个例子和第二个例子一样。
我认为你的主要观点是nub
和union
不依赖于可订购的类型(Ord
的实例),因此它只能做 O(N^ 2)。而任何使用可排序类型限制的方法(Map
、Set
、sort
等)都可以更快地完成。
在将代码发布到此处之前,我错误地更改了代码以获得更好的可读性。最初我也在第一个示例中使用集合。我为此道歉。由于 nulvinge 解决了我在标题中指出的问题,我接受他的回答。谢谢大家。【参考方案2】:
Map 创建了一个 O(1) 的 hashmap,但是使用函数你必须检查每个条件。
编辑:这实际上是错误的。 Map 是一个大小平衡的二叉树,但这应该会产生 O(logn) 复杂度,而函数必须检查每个组合,因此具有 O(n) 复杂度。
记住模式匹配的工作原理:
f 0 = 0
f i = 1+f (i-1)
是语法糖:
f i = if i == 0
then 0
else 1+f (i-1)
您实际上是在进行 O(n) 比较以找到您想要执行的函数。
使用 Map,它在二叉树中进行搜索,并且只需要进行 O(logn) 比较。
【讨论】:
它实际上是case
的语法糖,但它仍然是线性的。
这是否意味着在 Haskell 中使用枚举数据类型通常是个坏主意?因为最终您将编写函数来操作它们。
@André 不,这意味着你应该同时使用Map
和你的枚举类型。代替singleton "Hera"
,使用singleton Hera
——它可能更快,因为它不进行字符串比较!
感谢@Josh Lee 的澄清。我本来打算写 C 等价的,但它在 Haskell 问题中没有意义。
@nulvinge,如果您对模式匹配成本O(N)
的说法是正确的,那么地图查找将成本O(N*log(N))
因为compare
只能通过模式匹配来实现,所以您的解释远非完全的。另外,如果有的话,if
是用于模式匹配的语法糖,但反之则不然。以上是关于Map.Map 与函数的主要内容,如果未能解决你的问题,请参考以下文章