是否有人写过描述FIFO队列的Haskell类型类(或者是类型类的组合)。
Data.Collection.Sequence似乎太强了,但另一方面Data.Collection.Unfoldable似乎太弱了(因为订单没有定义)。
我只是想不重做别人的工作。
答案 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)。无论对ins
和get
的调用是如何交错的,相同的论证都会起作用 - 在两次调用中,每个元素都被删除一次,移动一次并去除一次。
答案 1 :(得分:2)