A FIFO queue provides an empty queue, an emptiness test, insertion at the rear, inspection of the front item, and removal of the front item. It preserves insertion order: after enqueuing and then , removing an item returns first. Inspection and removal on an empty FIFO queue must have a specified outcome, such as an exception handling case or an option-valued result. In a functional interface, insertion and removal return the new FIFO queue rather than updating their argument.
Use a two-list functional queue. Its representation Enqueue prepends to the reversed rear list. Removing the head either leaves a nonempty front or reverses the accumulated rear to restore the invariant. The result pair from
(front, rear) denotes the abstract sequence front @ rev rear; the front is nonempty unless the whole FIFO queue is empty. Normalize only when the front runs out:sml
datatype 'a queue = Q of 'a list * 'a list;
exception EmptyQueue;
fun check ([], rear) = Q (rev rear, [])
| check (front, rear) = Q (front, rear);
fun empty () = Q ([], []);
fun isEmpty (Q (front, _)) = null front;
fun enqueue x (Q (front, rear)) = check (front, x :: rear);
fun peek (Q ([], _)) = raise EmptyQueue
| peek (Q (x :: _, _)) = x;
fun dequeue (Q ([], _)) = raise EmptyQueue
| dequeue (Q (x :: front, rear)) = (x, check (front, rear));dequeue contains the removed element and the new FIFO queue. The constructor should be hidden behind an abstract interface so clients cannot construct an unnormalized representation that invalidates isEmpty.The two-list functional queue has constant-time
peek and isEmpty, but an individual insertion or removal can cost linear time when it reverses a rear list of length . Its useful guarantee is constant amortized time per operation along a single history starting from empty.For the amortized analysis, each item is consed into the rear once, transferred into the front at most once, and removed at most once. Charge its future reversal to the earlier insertion. Thus operations perform only total list-cell work. More formally, use potential , with sufficient to pay the per-cell reversal cost. An insertion into a nonempty front increases by and has constant actual cost; a reversal of cells decreases by , paying its actual cost. With and ,This amortized analysis is deterministic, not an average over a random workload. It assumes subsequent operations use the successive returned versions. If a persistent old version with a long rear is reused repeatedly, each independent dequeue can redo the same reversal. The simple potential accounting does not give constant amortized cost across such a branching history without further memoization or a stronger persistent FIFO queue implementation.
Articles by others on the same topic
There are currently no matching articles.