Functional Queues

13 Apr 2017 First-in first-out queues are a data structure used in many algorithms (including my favorite one—breadth-first search) and as a building block for complex functionality, the operations on queues should be as efficient as possible. This can be a bit of a challenge in a functional language: the bread-and-butter data structure of functional…

86

13 Apr 2017

First-in first-out queues are a recordsdata structure standard in quite a bit of algorithms (alongside with my favourite one—breadth-first search) and as a building block for advanced performance, the operations on queues must always be as efficient as that which that you may well well per chance be also articulate. This most steadily is rather of a speak in a purposeful language: the bread-and-butter recordsdata structure of purposeful programming, the list, supports rapidly insertion and deletion on the head—supreme for enqueuing or dequeuing respectively—nonetheless operations on the alternative terminate of the list assign O(n) time within the scale of the list. Need to we stop on our aspirations of purity and use mutable cells?

Luckily, a straightforward and orderly solution to this speak exists. By the use of two lists—one for enqueuing items and one for dequeuing them—we can use the lickety-split cons operation to push ingredients into one list and use pattern matching to pop them out of the alternative. If we strive to dequeue a fraction when the pop list is empty and the push list isn’t, we reverse the push list, transfer the ingredients into the pop list, and proceed.

Let’s see how we can put into effect this recordsdata form. We’ll have the option to use the OCaml language and we’ll delivery with a signature definition, i.e., the interface that our recordsdata form will must always give a enhance to.

module form QUEUE=sig
  form 'a t
  val empty    : 'a t
  val is_empty : 'a t -> bool
  val enqueue  : 'a -> 'a t -> 'a t
  val dequeue  : 'a t -> ('a option 'a t)
terminate

Within the occasion you aren’t accustomed to OCaml’s module machine, this declaration says that QUEUE is a module signature. A module implements this signature if it defines a form 'a t (t is the title, 'a is a form parameter; in Java it’d be written as T) and the functions empty, is_empty, enqueue, and dequeue. A consumer will manipulate our queue module by this interface and can by no approach be responsive to its implementation.

We are in a position to then elaborate a module known as Queue (it’s standard in OCaml, nonetheless no longer required, to use all caps to write the signature title and use Pascal-case for the implementation title).

module Queue : QUEUE=struct
  form 'a t=('a listing 'a listing)

  let empty=([], [])

  let is_empty=feature
    | ([], []) -> appropriate
    | (_, _)   -> false

  let enqueue x (pop, push)=
    (pop, x :: push)

  let rec dequeue=feature
    | ([], [])      -> (None, empty)
    | (x::xs, push) -> (Some x, (xs, push))
    | ([], push)    -> dequeue (List.rev push, [])
terminate

The variety 'a t is a tuple of two lists of 'a. We believe an empty queue by returning a tuple containing two empty lists and we verify that a queue is empty by pattern matching on the tuple and asserting that the 2 ingredients are the empty list. Each and every operations assign fixed time.

To enqueue a fraction, we use the cons operator (:: ) to add it to the entrance of the push list. Right here’s also a fixed time operation.

The dequeue feature is if truth be told the most attention-grabbing, and where the “magic” happens. If the pop list and the push list are empty, there are no ingredients to dequeue and we return None. If the pop list has a fraction in its pop list, we assign away it from the list and return it. When most productive the push queue has ingredients, we reverse them (to manufacture particular that the least fresh is on the head), fabricate that the original pop queue and invoke dequeue again.

Now, this final operation appears to be like to be O(n), nonetheless articulate we’ve got n calls to enqueue adopted by n calls to dequeue; the first dequeue takes time proportional to n (the list reversal), nonetheless all subsequent dequeues assign fixed time. This makes the operation O(1) amortised which is why it is acceptable in quite a bit of purposes.

And there which that you may well well bear it, a purely purposeful FIFO queue! For additional purely purposeful recordsdata constructions, please verify out Purely Functional Knowledge Structures by Chris Okasaki.

Read More

Ava Chan
WRITTEN BY

Ava Chan

I'm a researcher at Utokyo :) and a big fan of Ava MaxBio: About: