在O(1)中是否有一个带有Peek()的有界队列的纯功能实现?

时间:2011-06-27 04:59:53

标签: scala queue functional-programming

我想维护一个不可变的有界FIFO队列,我可以在一段时间后删除最旧的值。在Scala中,immutable.Queue适用于大小有限的队列(.size似乎是O(N),因为它在内部基于List,但我可以单独维护大小),但似乎没有便宜的方式来访问head元素以使用比O(N)便宜的任何东西来测试最旧值的年龄,所以我无法测试最旧条目的到期状态。任何指向纯函数(不可变)实现的指针?

4 个答案:

答案 0 :(得分:8)

这篇文章Haskell: Queues without pointers, 描述了具有O(1)摊销成本的纯功能队列(编辑:用于添加和删除元素)。我认为数据结构来自Chris Okasaki,更多细节在his book

基本思想是将队列分解为两个列表,一个用于前面,一个用于后面。新元素被添加到“前面”。 “Back”以相反的顺序存储,以便于弹出元素。当“后退”的所有元素都消失时,“前面”被反转并重新识别为“后退”。这些数据结构对于这些操作具有O(1)摊销成本,但显然有一些工作可以减少到O(1),正确。

编辑Okasaki's paper描述了一个优雅,纯粹功能的队列和双端队列(deques)实现。 Deques允许从任一端添加或删除元素。所有这些操作都是O(1),最坏的情况。

答案 1 :(得分:1)

Scala中的标准immutable.Queue可以根据需要进行调整,以实现摊销的复杂性。但请注意,peek操作将返回一个新队列,否则,对peek的连续调用可能都会在O(n)中完成。

您可以扩展Queue或创建一个完全适应它的新类。这是一个扩展它的版本:

import scala.collection._
import generic._
import immutable.Queue
import mutable.{ Builder, ListBuffer }

class MyQueue[+A] protected(in0: List[A], out0: List[A]) extends scala.collection.immutable.Queue[A](in0, out0) with GenericTraversableTemplate[A, MyQueue] with LinearSeqLike[A, MyQueue[A]] {
  override def companion: GenericCompanion[MyQueue] = MyQueue

  def peek: (A, MyQueue[A]) = out match {
    case Nil if !in.isEmpty => val rev = in.reverse ; (rev.head, new MyQueue(Nil, rev))
    case x :: xs            => (x, this)
    case _                  => throw new NoSuchElementException("dequeue on empty queue")
  }

  override def tail: MyQueue[A] =
    if (out.nonEmpty) new MyQueue(in, out.tail)
    else if (in.nonEmpty) new MyQueue(Nil, in.reverse.tail)
    else throw new NoSuchElementException("tail on empty queue")

  override def enqueue[B >: A](elem: B) = new MyQueue(elem :: in, out)

  // This ought to be override, but scalac doesn't think so!
  def enqueue[B >: A](iter: Iterable[B]) =
    new MyQueue(iter.toList.reverse ::: in, out)

  override def dequeue: (A, MyQueue[A]) = out match {
    case Nil if !in.isEmpty => val rev = in.reverse ; (rev.head, new MyQueue(Nil, rev.tail))
    case x :: xs            => (x, new MyQueue(in, xs))
    case _                  => throw new NoSuchElementException("dequeue on empty queue")
  }

  override def toString() = mkString("MyQueue(", ", ", ")")
}

object MyQueue extends SeqFactory[MyQueue] {
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, MyQueue[A]] = new GenericCanBuildFrom[A]
  def newBuilder[A]: Builder[A, MyQueue[A]] = new ListBuffer[A] mapResult (x => new MyQueue[A](Nil, x.toList))
  override def empty[A]: MyQueue[A] = EmptyQueue.asInstanceOf[MyQueue[A]]
  override def apply[A](xs: A*): MyQueue[A] = new MyQueue[A](Nil, xs.toList)

  private object EmptyQueue extends MyQueue[Nothing](Nil, Nil) { }
}

答案 2 :(得分:0)

如果我正确理解了这个问题,那么您正在寻找一个双端队列( deque )。 Okasaki,Kaplan和Tarjan有关于纯功能性deques的论文。至于实现,最简单的是我认为collection.immutable.IndexedSeq的默认实现是collection.immutable.Vector,根据this table估算head和{{1}的固定成本}}(它说last但我猜想tail也是O(1))。

Okasaki / Kaplan / Tarjan似乎已由Henry Ware实施。

我想到的另一个实现是Hintze的 FingerTree ,其中存在scala中的各种实现。 Scalaz有一段时间我放入a separate package,因为我经常使用它。根据Daniel Spiewak的演讲(我不记得我在哪里看到这个),虽然在恒定的时间因素下,FingerTree相当慢 - 而且Henry Ware的页面说它比其他实现慢。

答案 3 :(得分:0)

如果您要查找双端队列(双端队列),则Scala 1.13(2019年6月,八年后)现在有ArrayDeque

  

内部使用可调整大小的循环缓冲区的双端队列的实现。

     

追加,前置,removeFirst,removeLast和随机访问(索引查找和索引替换)需要分摊的固定时间。

     

通常,在第i个索引处的删除和插入为O(min(i, n-i)),因此从结尾/开头的插入和删除很快。

这来自scala/collection-strawman PR 490,在commit c0129af中合并到Scala。