2011-06-27 22 views
8

Quiero mantener una cola FIFO delimitada inmutable desde la que puedo eliminar los valores más antiguos después de un cierto tiempo. En Scala, el inmutable.Queue funciona bien para de tamaño limitado colas (.size parece ser O (N) ya que internamente se basa en List, pero puedo mantener el tamaño por separado), pero parece que no hay forma económica para acceder al elemento principal para probar la edad del valor más antiguo con algo más barato que O (N), por lo que no puedo probar el estado de caducidad de la entrada más antigua. ¿Alguna sugerencia para una implementación puramente funcional (inmutable)?¿Hay una implementación puramente funcional para una cola limitada con peek() en O (1)?

Respuesta

8

En este artículo, Haskell: Queues without pointers, describe una cola puramente funcional con O (1) coste amortizado (editar: para añadir y eliminar elementos). Creo que la estructura de datos proviene de Chris Okasaki y hay más detalles en his book.

La idea básica es descomponer la cola en dos listas, una para el frente y otra para la parte posterior. Los nuevos elementos se agregan a "frente". "Atrás" se almacena en orden inverso, para facilitar la aparición de elementos. Cuando todos los elementos de "atrás" desaparecen, "frente" se invierte y se vuelve a identificar como "atrás". Esta estructura de datos tiene O (1) costo amortizado para estas operaciones, pero aparentemente con algún trabajo puede reducirse a O (1), propiamente dicha.

Editar: Okasaki's paper describe una implementación elegante y puramente funcional de colas y colas de dos extremos (deques). Deques permite agregar o eliminar elementos de cualquier extremo. Todas esas operaciones son O (1), el peor de los casos.

+0

O (1) costo amortizado de _what_? No tiene sentido hablar de la complejidad de una operación sin decir cuál es esa operación. En este caso, la operación es dequerante, que es similar pero no es igual a mirar a escondidas. –

+0

Es O (1) costo amortizado por agregar y eliminar elementos. Al mirar, ¿te refieres a ver el elemento en la parte superior de la cola sin quitarlo? Esto se implementa fácilmente como una operación O (1): por ejemplo, se puede agregar trivialmente un campo adicional a la estructura de datos que recuerda el elemento superior. Mantener el tamaño funciona de la misma manera, como lo menciona Alex. –

+0

@Daniel, ahora me doy cuenta de que se refería a echar un vistazo al elemento al final de la cola. En mi respuesta, este es solo el encabezado de la lista "de nuevo", así que de nuevo O (1) vez. Si uno necesita echar un vistazo a múltiples valores al final de la cola, uno puede asegurarse de que la lista "atrás" sea siempre lo suficientemente grande. –

1

La norma immutable.Queue en Scala se puede adaptar para funcionar así, para una complejidad amortizada. Sin embargo, tenga en cuenta que la operación peek devolverá una nueva cola o, de lo contrario, las llamadas consecutivas a peek bien podrían hacerse en O (n).

Puede ampliar Queue o crear una clase totalmente nueva adaptándola. Aquí hay una versión extenderla:

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) { } 
} 
0

Si entiendo bien la pregunta, que busca una cola de doble extremo (deque ). Hay documentos de Okasaki, Kaplan y Tarjan sobre deques puramente funcionales. En cuanto a las puestas en práctica, lo más fácil es que creo que tomar la implementación predeterminada de collection.immutable.IndexedSeq que es collection.immutable.Vector, según this table haber estimado los costos constantes para head y last (se dice tail pero yo supongo que last es también O (1)).

El Okasaki/Kaplan/Tarjan uno parece haber sido implementado por Henry Ware.

La otra implementación que viene a la mente es el FingerTree de Hintze, para el que existen varias implementaciones en scala. Scalaz tiene uno que hace algún tiempo puse en a separate package ya que lo uso mucho. De acuerdo con una presentación de Daniel Spiewak (no recuerdo dónde vi esto), el FingerTree es bastante lento en los factores de tiempo constante, y también la página de Henry Ware dice que es más lenta que su otra implementación.

Cuestiones relacionadas