Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cslib.lean
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module -- shake: keep-all

public import Cslib.Algorithms.Lean.InsertionSort.InsertionSort
public import Cslib.Algorithms.Lean.MergeSort.MergeSort
public import Cslib.Algorithms.Lean.TimeM
public import Cslib.Computability.Automata.Acceptors.Acceptor
Expand Down
127 changes: 127 additions & 0 deletions Cslib/Algorithms/Lean/InsertionSort/InsertionSort.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/-
Copyright (c) 2026 Bhargav Kulkarni. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhargav Kulkarni
-/

module

public import Cslib.Algorithms.Lean.TimeM
public import Mathlib.Data.Nat.Cast.Order.Ring
public import Mathlib.Data.Nat.Lattice
public import Mathlib.Data.Nat.Log

@[expose] public section

/-!
# Insertion Sort on a list

In this file we introduce `insert` and `insertionSort` algorithms that returns a time monad
over the list `TimeM ℕ (List α)`. The time complexity of `insertionSort` is the number of comparisons.

--
## Main results

- `insertionSort_correct`: `insertionSort` permutes the list into a sorted one.
- `insertionSort_time`: The number of comparisons of `insertionSort` is at most `n^2`.

-/

set_option autoImplicit false

namespace Cslib.Algorithms.Lean.TimeM

variable {α : Type} [LinearOrder α]

/-- Inserts an element into a list, counting comparisons as time cost.
Returns a `TimeM ℕ (List α)` where the time represents the number of comparisons performed. -/
def insert : α → List α → TimeM ℕ (List α)
| x, [] => return [x]
| x, y :: ys => do
✓ let c := (x ≤ y : Bool)
if c then
return (x :: y :: ys)
else
let rest ← insert x ys
return (y :: rest)

/-- Sorts a list using the insertion sort algorithm, counting comparisons as time cost.
Returns a `TimeM ℕ (List α)` where the time represents the total number of comparisons. -/
def insertionSort (xs : List α) : TimeM ℕ (List α) := do
match xs with
| [] => return []
| x :: xs' => do
let sortedTail ← insertionSort xs'
insert x sortedTail

section Correctness

open List

@[grind →]
theorem mem_either_insert (xs : List α) (a b : α) (hz : a ∈ ⟪insert b xs⟫) : a = b ∨ a ∈ xs := by
fun_induction insert with
| case1 _ => grind
| case2 x y ys ih => grind

/-- A list is sorted if it satisfies the `Pairwise (· ≤ ·)` predicate. -/
abbrev IsSorted (l : List α) : Prop := List.Pairwise (· ≤ ·) l
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use List.SortedLE for this.

At minimum you can't have this duplicated because it will cause a failure in importing this alongside the merge sort module. This declaration Cslib.Algorithms.Lean.TimeM.IsSorted should probably never have been namespaced in this way.

Copy link
Contributor

@Shreyas4991 Shreyas4991 Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think SortedLE is deprecated in favour of Pairwise, so this usage is correct. The abbreviation is not necessary however.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MergeSort.lean also defines the IsSorted abbrev. So, I am not sure if I should inline it.

Copy link
Collaborator

@chenson2018 chenson2018 Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Shreyas4991 I'm not sure that is correct. The deprecation on List.Sorted does refer to List.Pairwise, but the comment specifically says to consider using SortedLE, which is not deprecated and has sortedLE_iff_pairwise as a grind = lemma. I think it is correct to use SortedLE here. (And in the merge sort file, but let's make a separate PR)


theorem sorted_insert {x : α} {xs : List α} (hxs : IsSorted xs) : IsSorted ⟪insert x xs⟫ := by
fun_induction insert x xs with
| case1 _ => simp
| case2 x y ys ih => grind [pairwise_cons]

theorem insertionSort_sorted (xs : List α) : IsSorted ⟪insertionSort xs⟫ := by
fun_induction insertionSort xs with
| case1 => simp
| case2 x xs ih => grind [sorted_insert]

lemma insert_perm x (xs : List α) : ⟪insert x xs⟫ ~ x :: xs := by
fun_induction insert with
| case1 x => simp
| case2 x y ys ih => grind

theorem insertionSort_perm (xs : List α) : ⟪insertionSort xs⟫ ~ xs := by
fun_induction insertionSort xs with
| case1 => simp
| case2 x xs ih =>
refine (insert_perm x (insertionSort xs).ret).trans ?_
grind [List.Perm.cons]

/-- Insertion Sort is functionally correct. -/
theorem insertionSort_correct (xs : List α) :
IsSorted ⟪insertionSort xs⟫ ∧ ⟪insertionSort xs⟫ ~ xs :=
⟨insertionSort_sorted xs, insertionSort_perm xs⟩

end Correctness

section TimeComplexity

/-- Time complexity of `insert`. -/
theorem insert_time (x : α) (xs : List α) : (insert x xs).time ≤ xs.length := by
fun_induction insert with
| case1 _ => simp
| case2 x y ys ih => grind

theorem insert_length (x : α) (xs : List α) : (insert x xs).ret.length = xs.length + 1 := by
fun_induction insert with
| case1 _ => simp
| case2 x y ys ih => grind

theorem insertionSort_length (xs : List α) : (insertionSort xs).ret.length = xs.length := by
fun_induction insertionSort xs with
| case1 => simp
| case2 x xs ih => grind [insert_length]

/-- Time complexity of `insertionSort`. -/
theorem insertionSort_time (xs : List α) : (insertionSort xs).time ≤ xs.length * xs.length := by
fun_induction insertionSort with
| case1 => simp
| case2 x xs ih =>
simp
grind [insertionSort_length, insert_time]

end TimeComplexity

end Cslib.Algorithms.Lean.TimeM
Loading