Существует ли чисто функциональная реализация для ограниченной очереди с peek () в O (1)? - PullRequest
8 голосов
/ 27 июня 2011

Я хочу сохранить неизменную ограниченную очередь FIFO, из которой я могу удалить самые старые значения через определенное время. В Scala immutable.Queue хорошо работает для ограниченных по размеру очередей (.size кажется O (N), поскольку он внутренне основан на List, но я могу поддерживать размер отдельно), но, похоже, не будет дешевого способа получить доступ к элементу head, чтобы проверить возраст самого старого значения с чем-либо более дешевым, чем O (N), поэтому я не могу проверить состояние истечения срока действия самой старой записи. Любые указатели на чисто функциональную (неизменяемую) реализацию?

Ответы [ 4 ]

8 голосов
/ 27 июня 2011

В этой статье, Haskell: очереди без указателей , описывается чисто функциональная очередь с амортизированной стоимостью O (1) ( edit : для добавления и удаления элементов).Я думаю, что структура данных исходит от Криса Окасаки, и более подробная информация содержится в его книге .

. Основная идея состоит в том, чтобы разбить очередь на два списка, один дляспереди и один сзади.Новые элементы добавляются в «front».«Назад» хранится в обратном порядке, чтобы облегчить выталкивание элементов.Когда все элементы «назад» пропали, «передний» переворачивается и снова идентифицируется как «задний».Эта структура данных имеет амортизированную стоимость O (1) для этих операций, но, по-видимому, при некоторой работе ее можно уменьшить до O (1), собственно.

Редактировать : Бумага Окасаки описывает элегантную, чисто функциональную реализацию очередей и двусторонних очередей (deques).Deques позволяет добавлять или удалять элементы с любого конца.Все такие операции O (1), наихудший случай.

1 голос
/ 27 июня 2011

Стандарт immutable.Queue в Scala можно адаптировать для такой работы, для амортизируемой сложности. Однако обратите внимание, что операция 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) { }
}
0 голосов
/ 12 июня 2019

Если вы ищете двустороннюю очередь (deque), Scala 1.13 (июнь 2019 г., восемь лет спустя) теперь имеет ArrayDeque

Реализация двусторонней очереди, которая внутренне использует циклический буфер изменяемого размера.

Append, prepend, removeFirst, removeLast и произвольный доступ (indexed-lookup и indexed-replace) занимают амортизированное постоянное время.

Как правило, удаления и вставки в i -ом индексе равны O(min(i, n-i)), и поэтому вставки и удаления из конца / начала выполняются быстро.

Это происходит от scala/collection-strawman PR 490 , объединенного со Scala в commit c0129af .

0 голосов
/ 28 июня 2011

Если я правильно понял вопрос, вы ищете двустороннюю очередь ( deque ).Есть статьи Окасаки, Каплана и Тарьяна о чисто функциональных заказах.Что касается реализаций, я думаю, что проще всего взять реализацию по умолчанию collection.immutable.IndexedSeq, которая равна collection.immutable.Vector, в соответствии с этой таблицей с оценочными постоянными затратами для head и last (в нем говоритсяtail но я бы предположил, что last также O (1)).

Кажется, что Okasaki / Kaplan / Tarjan был реализован Генри Уэйром .

Другая реализация, которая приходит на ум, - это FingerTree от Hintze, для которой существуют различные реализации в scala. Scalaz имеет тот, который некоторое время назад я положил в отдельный пакет , так как я часто его использую.Согласно презентации Даниэля Спивака (я не помню, где я это видел), FingerTree довольно медленный, хотя и с постоянными временными факторами, а также на странице Генри Уэра говорится, что он медленнее, чем его другая реализация.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...