【问题标题】:Best (mutable) queue data structure available in HaskellHaskell 中可用的最佳(可变)队列数据结构
【发布时间】:2021-10-04 19:47:00
【问题描述】:

尊敬的堆栈交换员,

我目前正在实现一些需要访问“队列”(FIFO)数据结构的算法。我正在使用 ST monad ,因此我正在寻找与 ST monad 的“内存可变性”相辅相成的队列实现。此时,我只是想在列表上使用 newSTRef (但同样,访问最后一个element 是 O(n) 复杂性,我想尽可能避免)。我也想过使用 Data.Sequence,但我不确定如果在 ST monad 中使用它是否真的会“可变” newSTRef 初始化。

Stack Exchange 的好心成员能否指导 Haskell 的初学者在上述情况下什么是最好的数据结构(或模块)?

【问题讨论】:

  • 使用指针和可变变量访问最后一个单元格是 O(1),如果您维护指向它的指针。并在您扩大(或​​缩小)列表时对其进行更新。
  • 哎呀!我需要在 Haskell 中查看指针!它们与这个 ST monad 是否特别兼容?
  • Data.STRef 说它是“(严格)ST monad 中的可变引用。”。引用是一个“指针”。一个 STRef 可以在其中包含另一个 STRef。当保存的 STRef 更新时,我们稍后检索它,检索到的 STRef 也会更新。因此它就像一个指针。你可以用它们重新实现列表,“节点”持有指向其他“节点”的“指针”。 --- 例如runST $ do { x <- newSTRef 11; xr <- newSTRef x; z <- writeSTRef x 8; x2 <- readSTRef xr; y <- read STRef x2, return y } 返回8

标签: haskell data-structures functional-programming queue mutable


【解决方案1】:

选项包括在STArray 之上实现传统的ring buffer,或使用由STRefs 构建的可变单链表,如下所示:

type CellRef s a = STRef s (Cell s a)
data Cell s a = End | Cell a (CellRef s a)
data Q s a = Q { readHead, writeHead :: CellRef s a }

如果您希望Q 易于增长,但又喜欢环形缓冲区的低指针开销,您可以通过让每个单元格都有一个慢慢填满的STArray 来获得中间立场。当它满了,分配一个新的单元格;当读取它清空它时,前进到下一个单元格。你明白了。

【讨论】:

  • 谢谢丹尼尔!我很难想象这个数据结构的外观。这让我想起了使用指针的(纯)C 数据结构的美好时光。我会试试这个,暂时我在 Haskell 中使用过 Data.Deque。
【解决方案2】:

FIFO 队列的标准实现是两个 LIFO 堆栈,一个包含从队列前面开始的项目(下一个要删除的项目在顶部),另一个包含从后面开始的项目(与最近推送的项目)。从队列中弹出时,如果前栈为空,则将其替换为后栈的反转。

如果两个堆栈都实现为 Haskell 列表,那么向队列中添加一个值是 O(1),如果数据结构以单线程方式使用,则移除一个值是摊销 O(1)。常数因素还不错。您可以将整个数据结构放在 STRef 中(保证单线程使用)。实现只是几行代码。您绝对应该优先执行此操作,而不是您的 O(n) 单列表想法。

您也可以使用Data.Sequence。与两栈队列一样,它是一种纯函数式数据结构,即对它的操作返回一个新的数据结构,而旧的数据结构保持不变。但是,就像两栈队列一样,您可以通过简单地将新数据结构写入保存旧数据结构的 STRef 来使其可变。 Data.Sequence 的常数因子可能比两栈队列差一点,但作为交换,您可以获得更大的高效操作集。

David Wagner 的答案中的可变列表可能效率较低,因为它需要队列中的每个项目两个堆对象。您可以通过编写在 GHC 中避免这种情况

Cell a {-# UNPACK #-} !(CellRef s a)

代替Cell a (CellRef s a)。不过,我不确定这会奏效。如果是这样,这可能比其他基于列表的方法要快一些。

【讨论】:

  • "...队列中每个项目有两个堆对象..." If only, if only, the woodpecker cries...
  • 谢谢本格! “但是,就像两栈队列一样,您可以通过将新数据结构简单地写入保存旧数据结构的 STRef 来使其可变。”我确实理解您如何要求我使用栈来实现队列. 另外,我必须仔细研究 David 的数据结构如何“每个项目两个对象”以及您的数据结构如何避免这种情况。非常感谢您的回复!
猜你喜欢
  • 2014-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-16
相关资源
最近更新 更多