Imports
import MathlibCLRS Section 10.1 - Stacks and queues
This section models stacks and queues both as functional lists and, matching the fourth-edition §10.1 "simple array-based data structures" interface, as array-backed stacks and queues with a top/head/tail pointer, overflow and underflow handling, and circular wrap-around. The list model captures the textbook algebra; the array model captures the bounded-storage reading while still deferring a concrete RAM execution layer.
Main results:
-
Theorem
pop_push: popping after pushing returns the pushed element and the old stack. -
Theorem
dequeue_enqueue_empty: enqueueing into an empty queue then dequeueing returns that element. -
Theorem
dequeue_enqueue_nonempty: enqueueing at the back of a nonempty queue does not change the next dequeued front element. -
Theorem
arrayPop_arrayPush: popping immediately after pushing an array-backed stack returns the pushed element and restores the top pointer. -
Theorem
arrayDequeue_arrayEnqueue_empty: enqueueing into an empty array-backed circular queue and then dequeueing returns the enqueued element. -
Theorem
arrayPush_overflow/arrayPop_empty/arrayEnqueue_overflow/arrayDequeue_empty: array overflow and underflow are reported asnone.
Status: proved for the functional-list and array-backed models.
Deferred refinements: RAM execution, pointer mutation, and memory costs.
namespace CLRSnamespace Chapter10Stacks
A functional stack is a list whose head is the stack top.
abbrev Stack (α : Type u) := List αThe empty stack.
def emptyStack : Stack α :=
[]Push an element onto the top of a stack.
Pop the top element from a stack, returning none on underflow.
Popping immediately after pushing recovers the pushed element and old stack.
Popping the empty stack reports underflow.
theorem pop_empty : pop (emptyStack : Stack α) = none := by
rflPushing increases stack length by one.
Queues
A functional queue is a list whose head is the dequeue front.
abbrev Queue (α : Type u) := List αThe empty queue.
def emptyQueue : Queue α :=
[]Enqueue an element at the back of the queue.
Dequeue the front element, returning none on underflow.
Dequeueing the empty queue reports underflow.
theorem dequeue_empty : dequeue (emptyQueue : Queue α) = none := by
rflEnqueueing into an empty queue and then dequeueing returns that element.
theorem dequeue_enqueue_empty (x : α) :
dequeue (enqueue x emptyQueue) = some (x, emptyQueue) := by
rflIf a queue is already nonempty, enqueueing at the back does not change the next front element to be dequeued.
theorem dequeue_enqueue_nonempty (front x : α) (rest : List α) :
dequeue (enqueue x (front :: rest)) = some (front, rest ++ [x]) := by
rflEnqueueing increases queue length by one.
theorem length_enqueue (x : α) (q : Queue α) :
(enqueue x q).length = q.length + 1 := by
simp [enqueue]Array-backed stacks and queues
A bounded array is modeled as a total indexed function; unwritten slots take a junk value. This is the functional interface of the fourth-edition §10.1 array, deferring RAM storage to a later execution model.
abbrev ArrayStore (α : Type u) := Nat → α
Read the element stored at index i of an array.
def arrayRead (A : ArrayStore α) (i : Nat) : α := A i
Write x at index i, leaving every other index unchanged.
def arrayWrite (i : Nat) (x : α) (A : ArrayStore α) : ArrayStore α :=
Function.update A i xReading immediately after writing returns the written value.
theorem arrayRead_arrayWrite_same (i : Nat) (x : α) (A : ArrayStore α) :
arrayRead (arrayWrite i x A) i = x := by
simp [arrayRead, arrayWrite]Writing at one index leaves every other index unchanged.
theorem arrayRead_arrayWrite_other {i j : Nat} (h : j ≠ i) (x : α) (A : ArrayStore α) :
arrayRead (arrayWrite i x A) j = arrayRead A j := by
simp [arrayRead, arrayWrite, h]
An array-backed stack of capacity n: a store together with a top
pointer and its capacity bound. Elements occupy slots 0..top-1 with the stack
top at index top-1 (CLRS §10.1).
structure ArrayStack (α : Type u) where
store : ArrayStore α
top : Nat
capacity : Nat
PUSH onto an array-backed stack: write at the current top and advance the top
pointer; returns none on overflow when the stack is already full.
def arrayPush (x : α) (s : ArrayStack α) : Option (ArrayStack α) :=
if s.top < s.capacity then
some { store := arrayWrite s.top x s.store, top := s.top + 1, capacity := s.capacity }
else
none
POP from an array-backed stack: return the top element and the stack with the
top pointer lowered; returns none on underflow when the stack is empty.
def arrayPop (s : ArrayStack α) : Option (α × ArrayStack α) :=
if s.top = 0 then
none
else
let t := s.top - 1
some (arrayRead s.store t, { store := s.store, top := t, capacity := s.capacity })Popping immediately after pushing (on a non-full stack) returns the pushed element and restores the top pointer; the freed slot keeps its value.
theorem arrayPop_arrayPush (x : α) (s : ArrayStack α) (h : s.top < s.capacity) :
(arrayPush x s).bind (fun s' => arrayPop s') =
some (x, { store := arrayWrite s.top x s.store, top := s.top, capacity := s.capacity }) := by
unfold arrayPush arrayPop
simp [h, arrayRead, arrayWrite]Popping an empty array-backed stack reports underflow.
theorem arrayPop_empty (f : Nat → α) (n : Nat) :
arrayPop ({ store := f, top := 0, capacity := n } : ArrayStack α) = none := by
simp [arrayPop]Pushing onto a full array-backed stack reports overflow.
theorem arrayPush_overflow (x : α) (s : ArrayStack α) (h : s.top = s.capacity) :
arrayPush x s = none := by
simp [arrayPush, h]
An array-backed circular queue of capacity n: a store with head and
tail pointers (CLRS §10.1). Following the textbook, the array holds at most
n-1 elements: the queue is empty when head = tail and full when
head = (tail + 1) mod n, with indices wrapping around.
structure ArrayQueue (α : Type u) where
store : ArrayStore α
head : Nat
tail : Nat
capacity : Nat
ENQUEUE: write at the tail and advance the tail (wrapping around); returns
none on overflow when the queue is full.
def arrayEnqueue (x : α) (q : ArrayQueue α) : Option (ArrayQueue α) :=
if q.head = (q.tail + 1) % q.capacity then
none
else
some { store := arrayWrite q.tail x q.store, head := q.head,
tail := (q.tail + 1) % q.capacity, capacity := q.capacity }
DEQUEUE: read at the head and advance the head (wrapping around); returns
none on underflow when the queue is empty.
def arrayDequeue (q : ArrayQueue α) : Option (α × ArrayQueue α) :=
if q.head = q.tail then
none
else
some (arrayRead q.store q.head,
{ store := q.store, head := (q.head + 1) % q.capacity,
tail := q.tail, capacity := q.capacity })Enqueueing into an empty array-backed queue and then dequeueing returns the enqueued element; the circular wrap leaves the tail one slot ahead of the head.
theorem arrayDequeue_arrayEnqueue_empty (x : α) (n : Nat) (f : Nat → α) (hn : 1 < n) :
(arrayEnqueue x { store := f, head := 0, tail := 0, capacity := n }).bind
(fun q' => arrayDequeue q') =
some (x, { store := arrayWrite 0 x f, head := 1 % n, tail := 1 % n, capacity := n }) := by
unfold arrayEnqueue arrayDequeue
simp [hn, Nat.mod_eq_of_lt, arrayRead, arrayWrite]Dequeueing an empty array-backed queue reports underflow.
theorem arrayDequeue_empty (f : Nat → α) (n : Nat) :
arrayDequeue ({ store := f, head := 0, tail := 0, capacity := n } : ArrayQueue α) = none := by
simp [arrayDequeue]Enqueueing into a full array-backed queue reports overflow.
theorem arrayEnqueue_overflow (x : α) (q : ArrayQueue α)
(h : q.head = (q.tail + 1) % q.capacity) :
arrayEnqueue x q = none := by
simp [arrayEnqueue, h]Enqueueing advances the tail pointer, wrapping modulo the capacity.
theorem arrayEnqueue_tail_wraps (x : α) (q : ArrayQueue α)
(h : q.head ≠ (q.tail + 1) % q.capacity) :
(arrayEnqueue x q).map (fun q' => q'.tail) = some ((q.tail + 1) % q.capacity) := by
simp [arrayEnqueue, h]end Chapter10end CLRS