我有Data.Vector
个Dog
个记录,每个记录都标识了所述狗所居住的House
。我需要一个查找例程来查找住在房子里的所有狗,如下所示,但是我需要不断的时间查找,这是第一个版本无法提供的。
dogs_by_houses dogs h = [ d | d <- Vec.toList dogs, h == house d ]
据我了解,优化Haskell代码的一个中心规则是编译器只在封闭lambda表达式内部计算每个表达式。因此,在绑定dogs
之前,我必须在dogs_by_houses dogs
表达式中为此特定h
构建一个查找表,是吗?
我认为Data.Vector
是完成此任务的最佳工具,尽管显然你不能像C ++向量那样缩小它们。我大致如下实现:
dogs_by_houses :: Vec.Vector Dog -> Int -> [Dog]
dogs_by_houses dogs = let {
dog_house = house_id . house ;
v0 = Vec.replicate (maximum . map dog_house $ Vec.toList dogs) [] ;
f v d = let { h = dog_house d } in v // [(h,d:v!h)] ;
dbh = Vec.foldl' f v0 dogs
} in (dbh !)
这里有什么非常愚蠢的优化明智吗?我认为像dbh
这样的变量上的严格标记不会有多大帮助,因为根据定义dogs
必须在dbh
有意义之前遍历。
使用MVector
和create
代替折叠返回修改后的不可变向量,这样做有什么大的优势吗?到目前为止,我尝试使用MVector
和create
的所有尝试必须不那么简洁,不同层次的do
或fold (>>)
类似构造或其他。我认为即使没有明确给出dbh
,编译器也应该简单地构建MVector
。
这个算法是否无法用列表实现?您偶尔会看到人们构建延迟无限的素数列表,然后使用primes !! n
选择第n个素数。我假设每次检索第n个素数需要遍历列表中的前n个素数。相反,我注意到GHC将字符串存储为C字符串,而不是列表。编译器是否只是将已知列表元素表示为数组而不是为每个元素重新遍历列表?
我使用了Paul Johnson和Louis Wasserman的答案来构建一个以这种方式索引任意向量的函数,因为我必须基于几个不同的索引函数来这样做。
vector_indexer idx vec = \i -> (Vec.!) t i
where m = maximum $ map idx $ Vec.toList vec
t = Vec.accumulate (flip (:)) (Vec.replicate m [])
$ Vec.map (\v -> (idx v, v)) vec
dogs_by_houses = vector_indexer (house_id . house)
我还没有对此进行过分析,但最终还是如此。我希望必须写my_d_by_h = dogs_by_houses my_dogs
并致电my_d_by_h
才能从索引中受益。
答案 0 :(得分:5)
我会用
构建表格Vec.accumulate (:) (Vec.replicate maxHouse [])
(Vec.map (\ d -> (dog_house d, d)) dogs)
肯定会分配最多一个中间向量,我怀疑它根本不足以分配任何中间向量。
答案 1 :(得分:5)
我曾经遇到过这样令人讨厌的事情。我使用Data.Map.Map作为查找表,但原理是相同的。我的函数获取了一个键值对列表,构造了一个Map,并返回了查找函数。它是这样的:
makeTable :: [(Key, Value)] -> Key -> Value
makeTable pairs = ((fromList pairs) !)
对我而言,我似乎可以写出类似
的内容myTable = makeTable [("foo", fooValue), ("bar", barValue) ... and so on]
然后我可以通过说
进行O(log N)查找v = myTable "foo"
然而,GHC实际上做的是从每个电话的列表重建整个地图。当你以这种方式创建一个部分应用程序时,GHC不会试图找出它可以从它得到的参数中得到哪些值,它只存储原始参数并为每个调用执行整个函数。完全合理的行为,但不是我想要的。
我必须写的是:
makeTable pairs = \k -> table ! k
where table = fromList pairs
我想你必须做同样的事情。