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