【问题标题】:Indexing a Haskell data structure for queries为查询索引 Haskell 数据结构
【发布时间】:2012-01-31 22:55:35
【问题描述】:

我有一个Data.VectorDog 记录,每个记录都标识了一个House 所说的狗住在哪里。我需要一个查找例程来查找住在房子里的所有狗,大致如下所示,但我需要持续时间查找,这是第一个版本无法提供的。

dogs_by_houses dogs h = [ d | d <- Vec.toList dogs, h == house d ]

据我了解,优化 Haskell 代码的核心规则是编译器只计算每个表达式在其封闭的 lambda 表达式中的一次。因此,在绑定h 之前,我必须在dogs_by_houses dogs 表达式中为这个特定的dogs 建立一个查找表,是吗?

我认为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 有意义之前遍历。

使用MVectorcreate 来代替折叠返回修改后的不可变向量有什么大的优势吗?到目前为止,我使用MVectorcreate 的所有尝试都必须不那么简洁,dos 或fold (&gt;&gt;) 的各个层类似于构造或其他。我认为编译器应该简单地构建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 才能从索引中受益。

【问题讨论】:

  • GHC 仅对编译时已知的字符串执行此操作。

标签: algorithm haskell vector indexing


【解决方案1】:

我曾经在做这样的事情时遇到了一个令人讨厌的问题。我使用 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 实际所做的是为每次调用从列表中重建整个 Map。当您以这种方式创建部分应用程序时,GHC 不会试图找出它可以从其获得的参数中派生出哪些值,它只是存储原始参数并为每次调用执行整个函数。完全合理的行为,但不是我想要的。

我不得不写的是:

makeTable pairs = \k -> table ! k
   where table = fromList pairs

我想你将不得不做同样的事情。

【讨论】:

  • 啊,当他们说“每个封闭的 lambda 表达式一次”时,他们的意思是 lambda 表达式,谢谢!
  • 我不确定如果我写了 "makeTable pairs = (table !) where... 会发生什么,这就是我认为你所拥有的。在第一个参数上使用 "trace"当你打电话给它时可能会很有启发性。
【解决方案2】:

我会用

Vec.accumulate (:) (Vec.replicate maxHouse []) 
  (Vec.map (\ d -> (dog_house d, d)) dogs)

它肯定最多分配一个中间向量,我怀疑它可能足够聪明,根本不分配任何中间向量。

【讨论】:

  • 糟糕,我在阅读Data.Vector时完全错过了累积功能,谢谢!
  • 是的。这是您在这里进行的那种索引的传统技巧。
猜你喜欢
  • 1970-01-01
  • 2012-02-18
  • 1970-01-01
  • 1970-01-01
  • 2012-05-13
  • 1970-01-01
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多