Queue的Haskell类型类

时间:2012-07-12 00:03:05

标签: haskell typeclass

是否有人写过描述FIFO队列的Haskell类型类(或者是类型类的组合)。

Data.Collection.Sequence似乎太强了,但另一方面Data.Collection.Unfoldable似乎太弱了(因为订单没有定义)。

我只是想不重做别人的工作。

2 个答案:

答案 0 :(得分:6)

在Haskell中滚动自己的FIFO队列实际上并不太难(也是一个有趣的练习)。我很感激你想要使用标准的类型类,这几乎肯定是你应该做的。但我上周才刚刚了解到这一点,我很兴奋不去写它。

这是一个简单的队列类,它允许您检查队列是否为空,从队列头部获取第一个元素(并返回队列的其余部分)并将新元素插入队列。

class Queue q where
  empty :: q a -> Bool
  get :: q a -> (a, q a)
  ins :: a -> q a -> q a

创建FIFO队列的最简单方法是使用列表:

instance Queue [] where
  empty = null

  get []     = error "Can't retrieve elements from an empty list"
  get (x:xs) = (x, xs)

  ins x xs = xs ++ [x]

然而,这非常低效。如果队列当前具有 n 元素,则插入新元素需要O( n )时间。如果要将 m 元素插入空队列,则需要O( m 2 )时间。我们可以创建一个在O(1)时间内(或至少是O(1)摊销时间)插入和检索元素的队列吗?

技巧是将队列的前端和后端存储在单独的列表中,队列的后端以相反的方式存储:

data Fifo a = F [a] [a]

instance Queue Fifo where

如果前面和后面都是空的,队列是空的:

  empty (F xs ys) = null xs && null ys

要在列表中插入一个新元素,我们只需将它放在后面队列中,这需要花费O(1)时间。

  ins y (F xs ys) = F xs (y:ys)

当有元素在那里等待时,从队列前面获取元素很容易(如果队列为空,我们会抛出错误)

  get (F []     []) = error "Can't retrieve elements from an empty queue"
  get (F (x:xs) ys) = (x, F xs ys)

最后,如果队列前面没有等待的元素,那么我们将队列的后面反转并将其放在前面。虽然这需要O( n )时间,但我们只需为每个元素执行一次,因此我们的get操作平均为O(1)时间:

  get (F [] ys) = get (F (reverse ys) [])

你有它 - 用功能语言分摊O(1)FIFO队列。


编辑: Efie在评论中询问了摊销的O(1)表现。摊销的常数时间的论点非常简单。

考虑将一系列 n 插入空队列,然后进行 n 检索。插入需要时间 n 。在第一次检索时,队列的前面是空的,所以我们必须反转队列的后面,这也需要时间 n ,再加上1来检索元素。最后,下一个 n - 1次检索每次需要1次,所以总时间为

n + n + 1 + n - 1 = 3 n

我们共进行了2次 n 次呼叫,因此摊销时间为3 n / 2 n = 3/2,即O (1)。无论对insget的调用是如何交错的,相同的论证都会起作用 - 在两次调用中,每个元素都被删除一次,移动一次并去除一次。

答案 1 :(得分:2)

取决于您想要的操作。 queuelikedequeue包具有队列的类型类。