-
Notifications
You must be signed in to change notification settings - Fork 150
simple functional queue from Okasaki #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
c-cube
wants to merge
16
commits into
leanprover:main
Choose a base branch
from
c-cube:c-cube/functionalQueue
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+348
−1
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
719a667
wip: basic functional queue in lean
c-cube 88bd163
remove @[simp] from some defs
c-cube ebe0603
simplify proofs and cleanup
c-cube 0f1a4d6
define queue with invariant
c-cube 47549d1
add Raw namespace
c-cube a9def53
wip: amortized analysis
c-cube b93816b
wip: define physicist method in Amortized.lean, start using it for th…
c-cube a3c0424
directly use amortized cost for proofs
c-cube 5fb8647
complexity result for list of operations
c-cube deda558
remove dead code
c-cube a3e6fc7
wtf man
c-cube 201ac9f
yay linearize yo partial order now
c-cube e18de8c
guess potential can be negative
c-cube 6677a7a
try to fix the missing TC
c-cube 650179c
fix
c-cube 0f950cb
unbundle
c-cube File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| /- | ||
| Copyright (c) 2026 Simon Cruanes. All rights reserved. | ||
| Released under Apache 2.0 license as described in the file LICENSE. | ||
| Authors: Simon Cruanes | ||
| -/ | ||
|
|
||
| module | ||
|
|
||
| import Cslib.Init | ||
| import Mathlib | ||
| public import Cslib.Algorithms.Lean.TimeM | ||
| public import Mathlib.Algebra.Ring.Defs | ||
| public import Mathlib.Order.Defs.PartialOrder | ||
| public import Mathlib.Algebra.Order.Ring.Defs | ||
| public import Mathlib.Algebra.Ring.Int.Defs | ||
| public import Mathlib.Algebra.Order.Ring.Int | ||
|
|
||
| /-! | ||
| # Amortized cost analysis | ||
|
|
||
| This complements `TimeM` in the cases where amortized costs are necessary. | ||
| -/ | ||
|
|
||
| @[expose] public section | ||
|
|
||
| namespace Cslib.Algorithms.Lean.Amortized | ||
|
|
||
| /-- Physicist method: a potential (lower bound on savings) defined on a | ||
| data structure -/ | ||
| class Potential φ α [CommRing φ] [LinearOrder φ] [IsStrictOrderedRing φ] where | ||
| /-- [Okasaki, *Purely Functional Data Structures*, 1996][okasaki1996] -/ | ||
| potential : α → φ | ||
|
|
||
| class Op α o where | ||
| applyOp : α → o → TimeM ℕ α | ||
|
|
||
| @[simp] def applyOps {α o : Type*} [Op α o] (x : α) (ops : List o) | ||
| : TimeM ℕ α := | ||
| List.foldlM (fun x op => Op.applyOp x op) x ops | ||
|
|
||
| /-- Amortized cost with the physicist's method, | ||
| following Okasaki, chapter 5 -/ | ||
| def amortizedCost {α o φ : Type*} | ||
| [Op α o] [CommRing φ] [LinearOrder φ] [IsStrictOrderedRing φ] [Potential φ α] | ||
| (x : α) (op : o) : φ := | ||
| Nat.cast (Op.applyOp x op).time | ||
| + Potential.potential (Op.applyOp x op).ret | ||
| - Potential.potential x | ||
|
|
||
| /-- If each operation's cost is bounded by `k`, then the amortized | ||
| cost over a series of operations is bounded by `k * ops.length`. -/ | ||
| theorem constantAmortizedCostL {α o φ : Type*} | ||
| [CommRing φ] [LinearOrder φ] [IsStrictOrderedRing φ] | ||
| [h_op : Op α o] [h_pot : Potential φ α] | ||
| (k : φ) (h_bounded : ∀ (x : α) (op : o), amortizedCost x op ≤ k) | ||
| (x : α) (ops : List o) | ||
| : (applyOps x ops).time | ||
| + Potential.potential (applyOps x ops).ret - Potential.potential x | ||
| ≤ k * Nat.cast ops.length | ||
| := by | ||
| simp only [applyOps] | ||
| revert x | ||
| induction ops with | ||
| | nil => | ||
| intro x | ||
| simp only [List.foldlM, TimeM.time_pure, CharP.cast_eq_zero, | ||
| TimeM.ret_pure, zero_add, sub_self, | ||
| List.length_nil, mul_zero, Std.le_refl] | ||
| | cons op ops2 h_ind => | ||
| intro x | ||
| simp only [amortizedCost] at h_bounded | ||
| simp only [List.foldlM, TimeM.time_bind, Nat.cast_add, TimeM.ret_bind, List.length_cons, | ||
| Nat.cast_one] | ||
| have bound1 := h_bounded x op | ||
| have bound2 := h_ind (Op.applyOp x op).ret | ||
| set applyOpX := (Op.applyOp x op : TimeM ℕ α) | ||
| set applyOps2 := (List.foldlM (fun x op => Op.applyOp x op) (Op.applyOp x op).ret ops2) | ||
| set potX := (Potential.potential x : φ) | ||
| set potOpX := (Potential.potential applyOpX.ret : φ) | ||
| set potOps2 := (Potential.potential applyOps2.ret : φ) | ||
| /- have potOpXPos := (Potential.potentialNonNegative (φ := φ) applyOpX.ret) -/ | ||
| ring_nf | ||
| have jfdoit := add_le_add bound1 bound2 | ||
| ring_nf at jfdoit | ||
| linarith | ||
|
|
||
| end Cslib.Algorithms.Lean.Amortized | ||
|
|
||
| end | ||
256 changes: 256 additions & 0 deletions
256
Cslib/Algorithms/Lean/FunctionalQueue/FunctionalQueue.lean
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| /- | ||
| Copyright (c) 2026 Simon Cruanes. All rights reserved. | ||
| Released under Apache 2.0 license as described in the file LICENSE. | ||
| Authors: Simon Cruanes | ||
| -/ | ||
|
|
||
| module | ||
|
|
||
| import Cslib.Init | ||
| import Mathlib | ||
| public import Mathlib.Algebra.Ring.Int.Defs | ||
| public import Mathlib.Algebra.Order.Ring.Int | ||
| public import Cslib.Algorithms.Lean.Amortized | ||
| public import Cslib.Algorithms.Lean.TimeM | ||
|
|
||
| /-! | ||
| # Functional Queue | ||
|
|
||
| A classic two-list queue with amortized O(1) `push` and `pop`. | ||
|
|
||
| The representation uses two lists: a front list (for dequeue) and a back list | ||
| (for enqueue). When the front list becomes empty, the back list is reversed | ||
| and becomes the new front. This yields amortized O(1) operations. | ||
|
|
||
|
c-cube marked this conversation as resolved.
|
||
| Cost model: each list cons is worth one `TimeM` tick. | ||
|
|
||
| ## References | ||
|
|
||
| * [Okasaki, *Purely Functional Data Structures*, 1996][okasaki1996] | ||
| -/ | ||
|
|
||
| set_option autoImplicit false | ||
|
|
||
| namespace Cslib.Algorithms.Lean | ||
|
|
||
| @[expose] public section | ||
|
|
||
| universe u | ||
|
|
||
| namespace Raw | ||
|
|
||
| structure FunctionalQueue (α : Type u) where | ||
| front : List α | ||
| back : List α | ||
|
|
||
| /-- Well-formedness: if front is empty, back must be empty too. -/ | ||
| def invariant {α : Type u} (q : Raw.FunctionalQueue α) : Prop := | ||
| q.front = [] → q.back = [] | ||
|
|
||
| /-- The logical contents of the queue: `front ++ back.reverse`. -/ | ||
| def ghostList {α : Type u} (q : FunctionalQueue α) : List α := | ||
| List.append q.front q.back.reverse | ||
|
|
||
| /-- The empty queue. -/ | ||
| def empty {α : Type u} : FunctionalQueue α := ⟨ [], [] ⟩ | ||
|
|
||
| /-- Internal: rebalance by moving `back.reverse` to `front` when `front` is empty. -/ | ||
| def rebalance {α : Type u} (q : FunctionalQueue α) | ||
| : TimeM ℕ (FunctionalQueue α) := | ||
| match q.front with | ||
| | [] => do | ||
| TimeM.tick q.back.length | ||
| pure ⟨ (q.back).reverse, [] ⟩ | ||
| | _ => pure q | ||
|
|
||
| theorem rebalanceInvert {α : Type u} (q : FunctionalQueue α) : | ||
| (rebalance q).ret.front = [] → q = empty := by | ||
| intro h | ||
| obtain ⟨f, b⟩ := q | ||
| simp only [rebalance, Raw.empty] at h ⊢ | ||
| split at h <;> simp_all | ||
|
|
||
| theorem rebalanceInvariant {α : Type u} {q : FunctionalQueue α} : | ||
| invariant (rebalance q).ret := by | ||
| obtain ⟨f, b⟩ := q | ||
| simp [rebalance, invariant] | ||
| split <;> grind | ||
|
|
||
| @[simp] theorem rebalanceIdempotent {α : Type u} (q : FunctionalQueue α) : | ||
| (rebalance (rebalance q).ret).ret = (rebalance q).ret := by | ||
| obtain ⟨f, b⟩ := q | ||
| simp [rebalance] | ||
| split <;> grind | ||
|
|
||
| @[simp] theorem rebalancePreserveGhost {α : Type u} (q : FunctionalQueue α) : | ||
| ghostList (rebalance q).ret = ghostList q := by | ||
| obtain ⟨f, b⟩ := q | ||
| simp [rebalance, ghostList] | ||
| split <;> grind [List.reverse_append] | ||
|
|
||
| /-- Enqueue an element. -/ | ||
| def push {α : Type u} (x : α) (q : FunctionalQueue α) | ||
| : TimeM ℕ (FunctionalQueue α) := do | ||
| TimeM.tick 1 | ||
| rebalance ⟨ q.front, x :: q.back ⟩ | ||
|
|
||
| theorem pushInvariant {α : Type u} (x : α) (q : FunctionalQueue α) | ||
| : invariant q → invariant (push x q).ret := by | ||
| intro h | ||
| rw [push] | ||
| apply rebalanceInvariant | ||
|
|
||
| theorem pushGhost {α : Type u} (x : α) (q : Raw.FunctionalQueue α) | ||
| : ghostList (push x q).ret = ghostList q ++ [x] := by | ||
| rw [push] | ||
| simp only [TimeM.ret_bind, rebalancePreserveGhost] | ||
| rw [ghostList] | ||
| simp [ghostList, List.append_assoc] | ||
|
|
||
| /-- Dequeue: returns `some (head, remaining)` or `none` if empty. -/ | ||
| def pop {α : Type u} (q : Raw.FunctionalQueue α) | ||
| : TimeM ℕ (Option (α × Raw.FunctionalQueue α)) := | ||
| match q.front with | ||
| | [] => pure none | ||
| | x :: tl => do | ||
| let q2 ← rebalance ⟨ tl, q.back ⟩ | ||
| pure (some (x, q2)) | ||
|
|
||
| theorem popInvariant {α : Type u} (x : α) (q q2 : FunctionalQueue α) | ||
| : invariant q → | ||
| (pop q).ret = some (x, q2) → | ||
| invariant q2 := by | ||
| intro hq hpop | ||
| obtain ⟨f, b⟩ := q | ||
| simp [invariant] at hq | ||
| unfold pop at hpop | ||
| cases f with | ||
| | nil => simp at hpop | ||
| | cons y tl => | ||
| simp only at hpop | ||
| obtain ⟨rfl, rfl⟩ := hpop | ||
| exact rebalanceInvariant | ||
|
|
||
| @[simp] theorem emptyInvariant {α : Type u} : invariant (@Raw.empty α) := by | ||
| simp [invariant, Raw.empty] | ||
|
|
||
| @[simp] theorem emptyGhost {α : Type u} : ghostList (@Raw.empty α) = [] := by | ||
| simp [ghostList, Raw.empty] | ||
|
|
||
| theorem popGhost {α : Type u} {x : α} {q q2 : Raw.FunctionalQueue α} | ||
| : invariant q → | ||
| (pop q).ret = some (x, q2) → | ||
| ghostList q = x :: ghostList q2 := by | ||
| intro hq hpop | ||
| obtain ⟨f, b⟩ := q | ||
| simp [invariant] at hq | ||
| unfold pop at hpop | ||
| cases f with | ||
| | nil => simp at hpop | ||
| | cons y tl => | ||
| simp only at hpop | ||
| obtain ⟨rfl, rfl⟩ := hpop | ||
| simp only [rebalancePreserveGhost] | ||
| simp [ghostList] | ||
|
|
||
| end Raw | ||
|
|
||
| namespace Complexity | ||
|
|
||
| def potential {α : Type u} (q : Raw.FunctionalQueue α) : ℤ := | ||
| q.back.length | ||
|
|
||
| instance functionalQueuePotential {α : Type u} | ||
| : Amortized.Potential ℤ (Raw.FunctionalQueue α) := | ||
| ⟨ potential ⟩ | ||
|
|
||
| inductive queueOp (α : Type u) where | ||
| | push : α → queueOp α | ||
| | pop | ||
|
|
||
| def applyOp {α : Type u} (q : Raw.FunctionalQueue α) (op : queueOp α) | ||
| : TimeM ℕ (Raw.FunctionalQueue α) := | ||
| match op with | ||
| | .push x => Raw.push x q | ||
| | .pop => do | ||
| match (← Raw.pop q) with | ||
| | none => pure q | ||
| | some (_, q2) => pure q2 | ||
|
|
||
| instance functionalQueueApplyOp {α : Type u} | ||
| : Amortized.Op (Raw.FunctionalQueue α) (queueOp α) := | ||
| ⟨ applyOp ⟩ | ||
|
|
||
| theorem potentialEmptyIsZero {α : Type u} | ||
| : potential (@Raw.empty α) = 0 := by | ||
| simp [potential, Raw.empty] | ||
|
|
||
| theorem amortizedCostQueueOp {α : Type u} (q : Raw.FunctionalQueue α) (op : queueOp α) | ||
| : Amortized.amortizedCost q op ≤ (2 : ℤ) := by | ||
| simp only [Amortized.amortizedCost, Amortized.Potential.potential, tsub_le_iff_right] | ||
| cases op with | ||
| | push x => | ||
| simp only [Amortized.Op.applyOp, applyOp, potential] | ||
| cases h_front : q.front <;> (rw [Raw.push, Raw.rebalance, h_front] at ⊢; grind) | ||
| | pop => | ||
| simp only [Amortized.Op.applyOp, applyOp, potential] | ||
| cases h_front : q.front <;> (rw [Raw.pop, h_front] at ⊢; grind [Raw.rebalance]) | ||
|
|
||
|
c-cube marked this conversation as resolved.
|
||
| /-- cost of applying operations to a queue -/ | ||
| theorem costQueueOps {α : Type u} | ||
| (q : Raw.FunctionalQueue α) (ops : List (queueOp α)) | ||
| : (Amortized.applyOps q ops).time | ||
| + potential (Amortized.applyOps q ops).ret | ||
| - potential q | ||
| ≤ (2 : ℤ) * ops.length | ||
| := by | ||
| have useful | ||
| := Amortized.constantAmortizedCostL 2 amortizedCostQueueOp q ops | ||
| simp only [Amortized.Potential.potential] | ||
| at useful | ||
| grind only | ||
|
|
||
| end Complexity | ||
|
|
||
| /-- A functional queue with invariant. -/ | ||
| @[ext] | ||
| structure FunctionalQueue (α : Type u) where | ||
| raw : Raw.FunctionalQueue α | ||
| inv : Raw.invariant raw | ||
|
|
||
| def empty {α : Type u} : FunctionalQueue α := ⟨ @Raw.empty α, Raw.emptyInvariant ⟩ | ||
|
|
||
| def push {α : Type u} (x : α) (q : FunctionalQueue α) | ||
| : TimeM ℕ (FunctionalQueue α) := | ||
| let r := Raw.push x q.raw | ||
| ⟨ ⟨ r.ret, Raw.pushInvariant x q.raw q.inv ⟩, r.time ⟩ | ||
|
|
||
| def pop {α : Type u} (q : FunctionalQueue α) | ||
| : TimeM ℕ (Option (α × FunctionalQueue α)) := | ||
| let r := Raw.pop q.raw | ||
| match h : r.ret with | ||
| | none => ⟨ none, r.time ⟩ | ||
| | some (x, q2) => | ||
| ⟨ some (x, ⟨ q2, Raw.popInvariant x q.raw q2 q.inv h ⟩), r.time ⟩ | ||
|
|
||
| /-- project to a list view, an ordered sequence of elements -/ | ||
| def ghostList {α : Type u} (q : FunctionalQueue α) : List α := Raw.ghostList q.raw | ||
|
|
||
| theorem pushGhost {α : Type u} (x : α) (q : FunctionalQueue α) : | ||
| ghostList (push x q).ret = ghostList q ++ [x] := | ||
| Raw.pushGhost x q.raw | ||
|
|
||
| theorem popGhost {α : Type u} {x : α} {q2 : FunctionalQueue α} : | ||
| ∀ {q : FunctionalQueue α}, | ||
| (pop q).ret = some (x, q2) → ghostList q = x :: ghostList q2 := by | ||
| intro q h | ||
| simp only [pop, ghostList] at h ⊢ | ||
| split at h | ||
| · simp only [reduceCtorEq] at h | ||
| · rename_i x2 q2' heq | ||
| obtain ⟨h1, h2⟩ := h | ||
| exact @Raw.popGhost α x q.raw q2' q.inv heq | ||
|
|
||
| end | ||
|
|
||
| end Cslib.Algorithms.Lean | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.