Queue
A queue is data structure that stores a sequence of elements, while supporting two basic operations: enqueue
and dequeue
Let's visualize how it works:
[0; 1; 2]
= { enqueue 42 }
[0; 1; 2; 42]
[0; 1; 2]
= { dequeue }
0, [1; 2]
First restriction: rely on inmutable data structures
- Using a list we can rely on the operator
::
for dequeueing, since pattern matchingx::xs
O(1) - However this means for enqueuing we need to do
xs @ [x]
which is O(n), wheren = xs.Length
Solution:
- rely on
::
for bothenqueue
anddequeue
, since the operationx::xs
is O(1) - We can use two lists one enqueueing and one for dequeueing,
front
andrear
respectively - For dequeueing elements we do the pattern match
x::xs
onfront
getting inx
the expected element, while inxs
the new value forfront
. - For enqueueing we repleace
rear
byx::rear
, however this list ends up reversed according the expected order when dequeueing, sincex
is its first element and should be the last dequeued. - At some point the
dequeue
operation will leave empty thefront
list. When that happens we can takerear
, reverse it and use it as the newfront
while the newrear
is an empty list.
type Queue<'a> = { front: 'a list; rear: 'a list }
let enqueue (q: Queue<'a>) (x: 'a) = { q with rear = x :: q.rear }
let dequeue (q: Queue<'a>) =
match q.front, List.rev q.rear with
| [], [] -> None
| [], x :: xs -> Some(x, { front = xs; rear = [] })
| x :: xs, _ -> Some(x, { q with front = xs })
let rec queueToSeq (q: Queue<'a>) =
seq {
match dequeue q with
| None -> ()
| Some(x, nq) ->
yield x
yield! queueToSeq nq
}
Let's make a simple test for our algorithm:
let xs = Seq.init 10 id |> Seq.toList
let q = xs |> Seq.fold (fun q x -> enqueue q x) {front = []; rear = []}
let ys = queueToSeq q |> Seq.toList
List.zip xs ys |> List.forall (fun (x, y) -> x = y)
True
Notice that in the current implementation of Microsoft's F# compiler, the following pattern match
match q.front, List.rev q.rear with
| [], [] -> ...
| [], x :: xs -> ...
| x :: xs, _ -> ...
evaluates List.rev q.rear
always before matching any branch. This means that the code could easily be made more efficient. However, I think it makes no sense to evaluate such an expression until after q.front
has been matched, since in some situations this would be sufficient to decide which branch to take. For this reason, I prefer to present the algorithm as it is, and leave it to the reader to optimise it if they think it is necessary.
type Queue<'a> =
{
front: 'a list
rear: 'a list
}
'a
type 'T list = List<'T>
val enqueue: q: Queue<'a> -> x: 'a -> Queue<'a>
val q: Queue<'a>
val x: 'a
Queue.rear: 'a list
val dequeue: q: Queue<'a> -> ('a * Queue<'a>) option
Queue.front: 'a list
Multiple items
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val rev: list: 'T list -> 'T list
union case Option.None: Option<'T>
val xs: 'a list
union case Option.Some: Value: 'T -> Option<'T>
val queueToSeq: q: Queue<'a> -> 'a seq
Multiple items
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>
val nq: Queue<'a>
val xs: int list
module Seq
from Microsoft.FSharp.Collections
val init: count: int -> initializer: (int -> 'T) -> 'T seq
val id: x: 'T -> 'T
val toList: source: 'T seq -> 'T list
val q: Queue<int>
val fold<'T,'State> : folder: ('State -> 'T -> 'State) -> state: 'State -> source: 'T seq -> 'State
val x: int
val ys: int list
val zip: list1: 'T1 list -> list2: 'T2 list -> ('T1 * 'T2) list
val forall: predicate: ('T -> bool) -> list: 'T list -> bool
val y: int