-
Notifications
You must be signed in to change notification settings - Fork 151
feat(algorithms): add stable bubble sort #575
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
Robertboy18
wants to merge
2
commits into
leanprover:main
Choose a base branch
from
Robertboy18:algorithms-bubble-sort-timem
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.
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
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,205 @@ | ||
| /- | ||
| Copyright (c) 2026 Robert Joseph George. All rights reserved. | ||
| Released under Apache 2.0 license as described in the file LICENSE. | ||
| Authors: Robert Joseph George | ||
| -/ | ||
|
|
||
| module | ||
|
|
||
| public import Cslib.Algorithms.Lean.Sorting | ||
| public import Cslib.Algorithms.Lean.TimeM | ||
| public import Mathlib.Data.Nat.Basic | ||
| public import Mathlib.Order.Lattice | ||
| public import Mathlib.Tactic.Attr.Core | ||
| public import Mathlib.Tactic.Finiteness.Attr | ||
| public import Batteries.Data.List.Lemmas | ||
|
|
||
| /-! | ||
| # Stable bubble sort on lists | ||
|
|
||
| `bubbleSort` returns a `TimeM ℕ (List α)`: the return value is the sorted list, and the time | ||
| component counts comparisons. | ||
|
|
||
| Each pass moves one maximal element to the end. Stability comes from the strict swap test: equal | ||
| elements are copied forward in their original order instead of being exchanged. | ||
| -/ | ||
|
|
||
| @[expose] public section | ||
|
|
||
| set_option autoImplicit false | ||
|
|
||
| namespace Cslib.Algorithms.Lean.TimeM | ||
|
|
||
| variable {α : Type} [LinearOrder α] | ||
|
|
||
| /-- | ||
| One stable bubbling pass. `bubbleMax candidate xs` returns a prefix and a final element. | ||
| The final element is maximal among `candidate :: xs`. | ||
|
|
||
| The pass swaps only when the next value is strictly smaller than the current candidate. Equal values | ||
| are therefore never swapped, which is the local reason the full sort is stable. | ||
| -/ | ||
| def bubbleMax : α → List α → TimeM ℕ (List α × α) | ||
| | candidate, [] => return ([], candidate) | ||
| | candidate, x :: xs => do | ||
| ✓ let swap := x < candidate | ||
| if swap then | ||
| let result ← bubbleMax candidate xs | ||
| return (x :: result.1, result.2) | ||
| else | ||
| let result ← bubbleMax x xs | ||
| return (candidate :: result.1, result.2) | ||
|
|
||
| /-- A bubbling pass keeps exactly one element out of the prefix. -/ | ||
| @[simp, grind =] | ||
| private theorem bubbleMax_length (candidate : α) (xs : List α) : | ||
| (⟪bubbleMax candidate xs⟫).1.length = xs.length := by | ||
| induction xs generalizing candidate with | ||
| | nil => simp [bubbleMax] | ||
| | cons x xs ih => | ||
| simp [bubbleMax] | ||
| split <;> simp [ih] | ||
|
|
||
| /-- A bubbling pass performs one comparison for each element it scans. -/ | ||
| @[simp, grind =] | ||
| private theorem bubbleMax_time (candidate : α) (xs : List α) : | ||
| (bubbleMax candidate xs).time = xs.length := by | ||
| induction xs generalizing candidate with | ||
| | nil => simp [bubbleMax] | ||
| | cons x xs ih => | ||
| simp [bubbleMax] | ||
| split <;> simp [ih, Nat.add_comm] | ||
|
|
||
| /-- Sorts a list using stable bubble sort, counting comparisons as time cost. -/ | ||
| def bubbleSort : List α → TimeM ℕ (List α) | ||
| | [] => return [] | ||
| | x :: xs => | ||
| let pass := bubbleMax x xs | ||
| let sortedPrefix := bubbleSort pass.ret.1 | ||
| ⟨sortedPrefix.ret ++ [pass.ret.2], pass.time + sortedPrefix.time⟩ | ||
| termination_by xs => xs.length | ||
| decreasing_by | ||
| simp_wf | ||
|
|
||
| section Correctness | ||
|
|
||
| /-- The final element produced by a bubbling pass is at least the initial candidate. -/ | ||
| @[simp] | ||
| private theorem bubbleMax_candidate_le_output (candidate : α) (xs : List α) : | ||
| candidate ≤ (⟪bubbleMax candidate xs⟫).2 := by | ||
| induction xs generalizing candidate with | ||
| | nil => simp [bubbleMax] | ||
| | cons x xs ih => | ||
| by_cases h : x < candidate | ||
| <;> simp only [bubbleMax, h, ↓reduceIte, ret_bind, ret_pure] | ||
| <;> grind | ||
|
|
||
| /-- Every element left in the prefix of a bubbling pass is bounded by the final element. -/ | ||
| @[simp] | ||
| private theorem bubbleMax_prefix_le_output (candidate : α) (xs : List α) : | ||
| ∀ z ∈ (⟪bubbleMax candidate xs⟫).1, z ≤ (⟪bubbleMax candidate xs⟫).2 := by | ||
| induction xs generalizing candidate with | ||
| | nil => simp [bubbleMax] | ||
| | cons x xs ih => | ||
| by_cases h : x < candidate | ||
| <;> simp only [bubbleMax, h, ↓reduceIte, ret_bind, ret_pure, List.mem_cons] | ||
| <;> intro z hz | ||
| <;> rcases hz with rfl | hz | ||
| · exact le_trans (le_of_lt h) (bubbleMax_candidate_le_output candidate xs) | ||
| · exact ih candidate z hz | ||
| · exact le_trans (le_of_not_gt h) (bubbleMax_candidate_le_output x xs) | ||
| · exact ih x z hz | ||
|
|
||
| /-- A bubbling pass is a permutation of the candidate and the remaining input. -/ | ||
| @[simp] | ||
| private theorem bubbleMax_perm (candidate : α) (xs : List α) : | ||
| ((⟪bubbleMax candidate xs⟫).1 ++ [(⟪bubbleMax candidate xs⟫).2]).Perm | ||
| (candidate :: xs) := by | ||
| induction xs generalizing candidate with | ||
| | nil => simp [bubbleMax] | ||
| | cons x xs ih => | ||
| by_cases h : x < candidate | ||
| · simpa only [bubbleMax, h, ↓reduceIte, ret_bind, ret_pure, List.cons_append] using | ||
| (List.Perm.cons x (ih candidate)).trans (List.Perm.swap candidate x xs) | ||
| · simpa only [bubbleMax, h, ↓reduceIte, ret_bind, ret_pure, List.cons_append] using | ||
| List.Perm.cons candidate (ih x) | ||
|
|
||
| /-- | ||
| A bubbling pass is stable. Since equal values are never swapped, filtering by any value gives the | ||
| same subsequence before and after the pass. | ||
| -/ | ||
| private theorem bubbleMax_stable (candidate : α) (xs : List α) : | ||
| StableByValue (candidate :: xs) | ||
| ((⟪bubbleMax candidate xs⟫).1 ++ [(⟪bubbleMax candidate xs⟫).2]) := by | ||
| induction xs generalizing candidate with | ||
| | nil => simp [StableByValue, bubbleMax] | ||
| | cons x xs ih => | ||
| intro y | ||
| by_cases h : x < candidate | ||
| · have := ih candidate y | ||
| by_cases hxy : x = y <;> by_cases hcy : candidate = y <;> grind [bubbleMax] | ||
| · have := ih x y | ||
| by_cases hcy : candidate = y <;> grind [bubbleMax] | ||
|
|
||
| /-- Bubble sort returns a permutation of its input. -/ | ||
| theorem bubbleSort_perm (xs : List α) : (⟪bubbleSort xs⟫).Perm xs := by | ||
| fun_induction bubbleSort xs with | ||
| | case1 => simp | ||
| | case2 x xs pass _ ih => | ||
| exact (List.Perm.append_right [pass.ret.2] ih).trans (by simp [pass]) | ||
|
|
||
| /-- Bubble sort is stable: equal values occur in the same order after sorting. -/ | ||
| theorem bubbleSort_stable (xs : List α) : StableByValue xs ⟪bubbleSort xs⟫ := by | ||
| fun_induction bubbleSort xs with | ||
| | case1 => simp [StableByValue] | ||
| | case2 x xs pass _ ih => | ||
| intro y | ||
| simp only [List.filter_append] | ||
| rw [ih y] | ||
| simpa [pass, List.filter_append] using bubbleMax_stable x xs y | ||
|
|
||
| /-- | ||
| Bubble sort returns a sorted list. The recursive call sorts the prefix, and `bubbleMax` proves every | ||
| prefix element is below the final element appended at the end. | ||
| -/ | ||
| theorem bubbleSort_sorted (xs : List α) : List.Pairwise (· ≤ ·) ⟪bubbleSort xs⟫ := by | ||
| fun_induction bubbleSort xs with | ||
| | case1 => simp | ||
| | case2 x xs pass _ ih => | ||
| change ((bubbleSort pass.ret.1).ret ++ [pass.ret.2]).Pairwise (· ≤ ·) | ||
| rw [List.pairwise_append] | ||
| have hprefix := bubbleMax_prefix_le_output x xs | ||
| have hperm := bubbleSort_perm pass.ret.1 | ||
| grind | ||
|
|
||
| /-- Bubble sort is functionally correct. -/ | ||
| theorem bubbleSort_correct (xs : List α) : List.Pairwise (· ≤ ·) ⟪bubbleSort xs⟫ ∧ | ||
| (⟪bubbleSort xs⟫).Perm xs ∧ StableByValue xs ⟪bubbleSort xs⟫ := by | ||
| exact ⟨bubbleSort_sorted xs, bubbleSort_perm xs, bubbleSort_stable xs⟩ | ||
|
|
||
| end Correctness | ||
|
|
||
| section TimeComplexity | ||
|
|
||
| /-- Bubble sort performs at most a full bubbling pass for each output position. -/ | ||
| theorem bubbleSort_time (xs : List α) : | ||
| let n := xs.length | ||
| (bubbleSort xs).time ≤ n * n := by | ||
| fun_induction bubbleSort xs with | ||
| | case1 => simp | ||
| | case2 x xs pass _ ih => | ||
| simp only [List.length_cons] | ||
| change pass.time + (bubbleSort pass.ret.1).time ≤ (xs.length + 1) * (xs.length + 1) | ||
| have hpass : pass.ret.1.length = xs.length := by simp [pass] | ||
| calc | ||
| pass.time + (bubbleSort pass.ret.1).time | ||
| ≤ pass.time + pass.ret.1.length * pass.ret.1.length := by omega | ||
| _ = xs.length + pass.ret.1.length * pass.ret.1.length := by simp [pass] | ||
| _ = xs.length + xs.length * xs.length := by rw [hpass] | ||
| _ = xs.length * (xs.length + 1) := by simp [Nat.mul_succ, Nat.add_comm] | ||
| _ ≤ (xs.length + 1) * (xs.length + 1) := | ||
| Nat.mul_le_mul_right (xs.length + 1) (Nat.le_succ xs.length) | ||
|
|
||
| end TimeComplexity | ||
|
|
||
| end Cslib.Algorithms.Lean.TimeM | ||
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,28 @@ | ||
| /- | ||
| Copyright (c) 2026 Robert Joseph George. All rights reserved. | ||
| Released under Apache 2.0 license as described in the file LICENSE. | ||
| Authors: Robert Joseph George | ||
| -/ | ||
|
|
||
| module | ||
|
|
||
| public import Cslib.Init | ||
|
|
||
| /-! | ||
| # Sorting utilities | ||
|
|
||
| For stable list sorts, filtering the input and output by any value records the relative order of | ||
| the copies of that value. | ||
| -/ | ||
|
|
||
| @[expose] public section | ||
|
|
||
| set_option autoImplicit false | ||
|
|
||
| namespace Cslib.Algorithms.Lean | ||
|
|
||
| /-- `ys` preserves the order of equal values from `xs`. -/ | ||
| abbrev StableByValue {α : Type*} [DecidableEq α] (xs ys : List α) : Prop := | ||
| ∀ value, ys.filter (fun x => x = value) = xs.filter (fun x => x = value) | ||
|
Robertboy18 marked this conversation as resolved.
Outdated
|
||
|
|
||
| end Cslib.Algorithms.Lean | ||
Oops, something went wrong.
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.