【问题标题】:Haskell: Datastruture with O(1) append and O(1) indexing?Haskell:具有 O(1) 附加和 O(1) 索引的数据结构?
【发布时间】:2012-05-13 11:11:35
【问题描述】:

我在 Haskell 中寻找一种同时支持快速索引和快速追加的数据结构。这是针对递归引起的记忆问题。

从向量在 c++ 中的工作方式(它是可变的,但在这种情况下这不重要)看来,具有(摊销)O(1) 附加和 O(1) 索引的不可变向量应该是可能的(好的,不是的,请参阅这个问题的 cmets)。这在 Haskell 中是否可行,或者我应该使用具有(AFAICT 无论如何)O(1) 附加和 O(log(min(i,n-i))) 索引的 Data.Sequence?

在相关的说明中,作为一名 Haskell 新手,我发现自己渴望一份实用、简洁的 Haskell 数据结构指南。理想情况下,这将对最实用的数据结构以及性能特征和实现它们的 Haskell 库的指针提供相当全面的概述。似乎那里有很多信息,但我发现它有点分散。我是不是要求太多了?

【问题讨论】:

  • 我很确定不存在这样的数据结构。如果你所有的追加都是线性发生的,使用Data.Vector,因为融合会给你想要的性能,否则使用Data.Sequence
  • 谢谢。我认为这应该是可能的,但也许是专业的......你能解释一下“你所有的附加都是线性发生的”是什么意思吗?你的意思是线性增加的指数?似乎使用向量每个操作仍然是 O(n) ...
  • 基本上,如果您可以将向量视为以不保存中间值的方式通过代码“线程化”,则可以将附加“融合”到单个操作中。跨度>
  • "从向量在 c++ 中的工作方式(它是可变的,但在这种情况下应该无关紧要)看来,具有 O(1) 附加和 O(1) 索引的不可变向量应该是可能的”。您如何想象这样的数据结构如何工作?它如何避免在插入时复制数据,并且该操作仍然具有O(1) 复杂性?简而言之,我认为你在这里错了。 C++ 向量的可变性确实在这里很重要。
  • @Paul:嗯,不变性要求您可以对具有两个不同值的同一个基向量执行附加操作,结果应该产生两个不同的向量。这对于您描述的方案是不可能的(向量必须至少复制一次)。

标签: haskell data-structures vector


【解决方案1】:

对于简单的记忆问题,您通常希望构建表一次,以后不要修改它。在这种情况下,您可以避免担心追加,而是将记忆表的构造视为一项操作。

一种方法是利用惰性求值并在我们构建表格时引用它。

import Data.Array
fibs = listArray (0, n-1) $ 0 : 1 : [fibs!(i-1) + fibs!(i-2) | i <- [2..n-1]]
  where n = 100

当表格元素之间的依赖关系导致难以提前制定简单的评估顺序时,此方法特别有用。但是,它需要使用盒装数组或向量,由于额外的开销,这可能会使这种方法不适合大型表。

对于未装箱的向量,您可以使用constructN 之类的操作,它可以让您以纯粹的方式构建表格,同时在下面使用突变来提高效率。它通过给函数传递一个不可变的视图来实现这一点,该视图是迄今为止构建的向量的前缀,然后您可以使用它来计算下一个元素。

import Data.Vector.Unboxed as V
fibs = constructN 100 f
  where f xs | i < 2 = i
             | otherwise = xs!(i-1) + xs!(i-2)
             where i = V.length xs

【讨论】:

  • 哇,这个constructN 太棒了。
  • 我可能弄错了,但我认为这将超过 Int 的大小,并且 Data.Vector.Unboxed 中没有 Unbox Integer 实例。
  • @DougMoore:是的,这会溢出。重点是为了说明记忆,而不是提供计算斐波那契数的好方法。为此,有更好的算法不需要任何记忆:)
  • @hammar 我知道,我很少发表评论。 :)
  • @hammar 谢谢你的回答。我不太确定我的问题是否足够简单以适应这种方法(它使用记忆固定点函数 + 一个给出终端状态的函数 + 一个更新规则,它采用(可能)自身的记忆版本)。我得考虑一下。
【解决方案2】:

如果没有记忆,C++ 向量将实现为具有边界和大小信息的数组。当插入会增加超出大小的范围时,大小会加倍。这是分期的 O(1) 时间插入(不是您声称的 O(1)),并且可以使用 Array 类型在 Haskell 中很好地模拟,可能需要适当的 IO 或 ST 前置。

【讨论】:

  • 是的,它是摊销 O(1),我知道,忘了提。我将检查 Array,您是否有与 IO 或 ST 一起学习它的好资源?如果可能的话,我希望代码是纯的,并且没有使用 ST monad 的经验。
  • 这段代码不会是纯粹的。那是不可能的。可变性与非可变性确实有所不同。
  • @Paul Lazy Functional State Threads 是原始的 ST 论文,我猜。
  • @Paul 我已经完成了前 1% 的编写库的工作,stuck it on hpaste。我放弃上传到 hpaste 后留下的所有版权。您可能想要扩展 API、更改名称、基准测试并做所有这些好事,但它至少应该让您在 ST monad 中进行编程以帮助您入门。
  • 在纯粹的 CS 世界中,您可以同时拥有这两者:一个无限大小的数组,其中包含指向所使用的最后一个元素的指针——无需复制。好吧,也许你不相信无限大小,但显然我们可以通过使用更大的 RAM 块来尽可能少地复制。指数增长的动态数组在有意义的维度上是最优的。
【解决方案3】:

查看this,以更明智地选择您应该使用的内容。

但简单的一点是,如果你想要 C++ 向量的等价物,请使用Data.Vector。

【讨论】:

  • 它们还具有安全索引运算符的优势,由于某种原因缺少该数组
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-14
  • 2011-06-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多