theorem prop_NStoogeSort2IsSort (xs: List Nat) : nstoogesort2' xs == isort xs:= by sorry

import Mathlib

def third : Nat → Nat
| 0 => 0
| 1 => 0
| 2 => 0
| n + 3 => 1 + (third n)

def insert' : Nat → List Nat → List Nat
| x, [] => [x]
| x, y::xs => if x <= y then x::y::xs else y::(insert x xs)

def isort: List Nat → List Nat
| [] => []
| x::xs => insert' x (isort xs)

def splitAt : Nat → List Nat → (List Nat × List Nat)
| _n, [] => ([], [])
| 0, xs => ([], xs)
| n + 1, x::xs => match splitAt n xs with
  | (l1, l2) => (x::l1, l2)

def reverse : List Nat → List Nat
| [] => []
| x::xs => (reverse xs) ++ [x]

lemma len_rev_eq_len {l: List Nat} : (reverse l).length = l.length := by
  induction l with
  | nil => simp [reverse]
  | cons head tail ih => unfold reverse; simp [ih];

lemma splitAt_len_le : (splitAt n xs).2.length ≤ xs.length := by
  induction xs generalizing n with
  | nil => unfold splitAt; simp;
  | cons head tail ih =>
    cases n with
    | zero => unfold splitAt; simp;
    | succ nm1 => unfold splitAt; simp; apply Nat.le.step; exact ih;

lemma splitAt_second_len_lt (n: Nat): (splitAt n.succ (x::xs)).2.length < (x::xs).length := by
  unfold splitAt;
  simp;
  calc List.length (splitAt n xs).2 ≤ (List.length xs) := splitAt_len_le
       _                          < Nat.succ (List.length xs) := Nat.lt_succ_self _

lemma splitAt_second_len_lt' (n: Nat) (hlen: xs.length > 0): (splitAt n.succ xs).2.length < xs.length := by
  cases xs with
  | nil => simp at hlen;
  | cons x xs => exact splitAt_second_len_lt _;

lemma splitAt_second_len_lt'' (hn: n > 0) (hlen: xs.length > 0) (hlen': xl = xs.length): (splitAt n xs).2.length < xl := by
  cases n with
  | zero => simp at hn;
  | succ nm1 => rw [hlen']; exact splitAt_second_len_lt' _ hlen;

lemma splitAt_sum_preserves_len (n: Nat) (xs: List Nat) (hspl: spl = splitAt n xs):
  (spl.1.length + spl.2.length = xs.length) := by
  induction xs generalizing n spl with
  | nil => simp [splitAt] at hspl; simp [hspl];
  | cons head tail ih => cases n with
    | zero => simp [splitAt] at hspl; simp [hspl];
    | succ nm1 =>
      simp [splitAt] at hspl;
      simp [hspl]
      rw [Nat.succ_add];
      apply Order.succ_eq_succ_iff.2
      exact ih nm1 (by rfl);

lemma third_eq_div_3 : (x/3) = third x := by
  induction x using Nat.strongInductionOn with
  | ind x ih =>
  unfold third
  match x with
  | 0 => simp;
  | 1 => simp;
  | 2 => simp;
  | n + 3 => simp [Nat.succ_eq_add_one]; ring_nf; linarith [ih n (by linarith)]

def nstoogesort (xs : {xs : List Nat // xs.length = n}) : {ys: List Nat // ys.length = n} := match xs with
| ⟨[], h⟩ => ⟨[], h⟩
| ⟨[x], h⟩ => ⟨[x], h⟩
| ⟨[x, y], h⟩ => if x <= y then ⟨[x, y], h⟩ else ⟨[y, x], by simp [←h]⟩
| ⟨x1::x2::x3::xs, hlen⟩ =>
  let yzs1 := splitAt (third (x1::x2::x3::xs).length) (reverse (x1::x2::x3::xs))
  let ⟨tmp2, hlen2⟩ := (@nstoogesort yzs1.2.length ⟨yzs1.2, by rfl⟩)
  let s1s2a := tmp2 ++ (reverse yzs1.1)
  let yzs2 := splitAt (third s1s2a.length) s1s2a
  let ⟨tmp3, hlen3⟩ := (@nstoogesort yzs2.2.length ⟨yzs2.2, by rfl⟩)
  let s1s1 := yzs2.1 ++ tmp3
  let yzs3 := splitAt (third s1s1.length) (reverse s1s1)
  let ⟨tmp4, hlen4⟩ := (@nstoogesort yzs3.2.length ⟨yzs3.2, by rfl⟩)
  ⟨tmp4 ++ (reverse yzs3.1), by
    simp [hlen4, len_rev_eq_len];
    rw [add_comm];
    simp [splitAt_sum_preserves_len _ _ (Eq.refl yzs3), len_rev_eq_len];
    unfold_let s1s1
    simp [hlen3, splitAt_sum_preserves_len _ _ (Eq.refl yzs2)]
    unfold_let s1s2a
    simp [hlen2, len_rev_eq_len];
    rw [add_comm];
    simp [splitAt_sum_preserves_len _ _ (Eq.refl yzs1), len_rev_eq_len, hlen]
    ⟩
termination_by match xs with | ⟨lst, _h⟩ => lst.length
decreasing_by
  simp_wf
  apply splitAt_second_len_lt''
    (by simp [←third_eq_div_3, Nat.succ_eq_add_one, Nat.add_assoc])
    (by simp [len_rev_eq_len, Nat.succ_eq_add_one, Nat.add_assoc])
    (by simp [len_rev_eq_len, hlen])
  simp_wf
  suffices hyzs2 : (splitAt (List.length s1s2a / 3) s1s2a).2.length < n
  rw [List.length_append, third_eq_div_3] at hyzs2
  exact hyzs2;
  unfold_let s1s2a
  suffices hyzs1 : List.length (tmp2 ++ reverse yzs1.1) = List.length xs + 3
  exact splitAt_second_len_lt'' (by simp [hyzs1]) (by simp [hyzs1]) (by simp [hyzs1, ←hlen])
  simp [hlen2, len_rev_eq_len, Nat.add_comm, splitAt_sum_preserves_len _ _ (Eq.refl yzs1)]
  ring_nf
  simp_wf
  suffices hyzs3 : (splitAt (List.length s1s1 / 3) (reverse s1s1)).2.length < n
  unfold_let s1s1 yzs2 s1s2a yzs1 at hyzs3
  simp [List.length_append, third_eq_div_3] at hyzs3;
  exact hyzs3;
  suffices hs1s1: s1s1.length = xs.length + 3
  exact splitAt_second_len_lt'' (by simp [hs1s1]) (by simp [len_rev_eq_len, hs1s1]) (by simp [len_rev_eq_len, hs1s1, ←hlen])
  unfold_let s1s1
  simp [hlen3, splitAt_sum_preserves_len _ _ (Eq.refl yzs2)]
  unfold_let s1s2a
  simp [hlen2, len_rev_eq_len, Nat.add_comm, splitAt_sum_preserves_len _ _ (Eq.refl yzs1)]
  ring_nf

def nstoogesort2' (xs: List Nat) := nstoogesort ⟨xs, (by rfl)⟩

xs : List ℕ
⊢ (↑(nstoogesort2' xs) == isort xs) = true


theorem hpairwise_desc (ps: List MyHeap): List.length (hpairwise ps) ≤ List.length ps := by sorry

import Mathlib

inductive MyHeap where
| nil : MyHeap
| node : MyHeap → Nat → MyHeap  →  MyHeap

def hmerge : MyHeap  → MyHeap  → MyHeap
| MyHeap.nil, q => q
| p, MyHeap.nil => p
| MyHeap.node p x q, MyHeap.node r y s =>
  if x <= y then MyHeap.node (hmerge q (MyHeap.node r y s)) x p
            else MyHeap.node (hmerge (MyHeap.node p x q) s) y r

def hpairwise : List MyHeap → List MyHeap
| p::q::qs => (hmerge p q)::hpairwise qs
| ps => ps

ps : List MyHeap
⊢ (hpairwise ps).length ≤ ps.length


theorem numElem_lt_subHeaps  (q r: MyHeap) {x: Nat}: numElem q < numElem (MyHeap.node q x r) ∧ numElem r < numElem (MyHeap.node q x r) := by sorry

import Mathlib

inductive MyHeap where
| nil : MyHeap
| node : MyHeap → Nat → MyHeap  →  MyHeap

def numElem : MyHeap → Nat
| MyHeap.nil => 0
| MyHeap.node p _x q => 1 + numElem p + numElem q

q r : MyHeap
x : ℕ
⊢ numElem q < numElem (q.node x r) ∧ numElem r < numElem (q.node x r)


theorem merge_elems (p q: MyHeap): numElem p + numElem q = numElem (hmerge p q) := by sorry

import Mathlib

inductive MyHeap where
| nil : MyHeap
| node : MyHeap → Nat → MyHeap  →  MyHeap

def hmerge : MyHeap  → MyHeap  → MyHeap
| MyHeap.nil, q => q
| p, MyHeap.nil => p
| MyHeap.node p x q, MyHeap.node r y s =>
  if x <= y then MyHeap.node (hmerge q (MyHeap.node r y s)) x p
            else MyHeap.node (hmerge (MyHeap.node p x q) s) y r

def numElem : MyHeap → Nat
| MyHeap.nil => 0
| MyHeap.node p _x q => 1 + numElem p + numElem q

p q : MyHeap
⊢ numElem p + numElem q = numElem (hmerge p q)


theorem numElem_merge_branches_lt (p q: MyHeap) (x: Nat): numElem (hmerge p q) < numElem (MyHeap.node p x q) := by sorry

p q : MyHeap
x : ℕ
⊢ numElem (hmerge p q) < numElem (p.node x q)


theorem len_pairwise (xs: List (List Nat)): 2 * (pairwise xs).length = (if (Odd xs.length) then xs.length + 1 else xs.length) := by sorry

import Mathlib

def lmerge : List Nat → List Nat → List Nat
| [], ys => ys
| xs, [] => xs
| x::xs, y::ys => if  x <= y  then
      x::(lmerge xs (y::ys))
    else
      y::(lmerge (x::xs) ys)

def pairwise : List (List Nat) → List (List Nat)
| xs::ys::xss => lmerge xs ys :: pairwise xss
| xss => xss

xs : List (List ℕ)
⊢ 2 * (pairwise xs).length = if Odd xs.length then xs.length + 1 else xs.length


theorem merge_term : (pairwise (xs::ys::xss)).length < (xs::ys::xss).length := by sorry

xs ys : List ℕ
xss : List (List ℕ)
⊢ (pairwise (xs :: ys :: xss)).length < (xs :: ys :: xss).length


import Mathlib

def half : Nat → Nat
| 0 => 0
| 1 => 0
| x + 2 => 1 + (half x)


theorem len_evens_le {xs: List Nat}: (evens xs).length ≤ xs.length := by sorry

import Mathlib

mutual
  def evens : List Nat → List Nat
  | [] => []
  | x::xs => x::(odds xs)
--
  def odds : List Nat → List Nat
  | [] => []
  | _x::xs => evens xs
end

xs : List ℕ
⊢ (evens xs).length ≤ xs.length


theorem len_odds_le {xs: List Nat}: (odds xs).length ≤ xs.length := by sorry

xs : List ℕ
⊢ (odds xs).length ≤ xs.length


theorem bmerge_term (a b: Nat) (as bs: List Nat) (hlen: ¬(List.length as == 0 && List.length bs == 0) = true): List.length (evens (a :: as)) + List.length (evens (b :: bs)) < Nat.succ (List.length as) + Nat.succ (List.length bs) := by sorry

a b : ℕ
as bs : List ℕ
hlen : ¬(as.length == 0 && bs.length == 0) = true
⊢ (evens (a :: as)).length + (evens (b :: bs)).length < as.length.succ + bs.length.succ


theorem bmerge_term2 (x y: Nat) (xs ys: List Nat) : List.length (odds (x :: xs)) + List.length (odds (y :: ys)) < Nat.succ (List.length xs) + Nat.succ (List.length ys) := by sorry

x y : ℕ
xs ys : List ℕ
⊢ (odds (x :: xs)).length + (odds (y :: ys)).length < xs.length.succ + ys.length.succ


theorem bsort_term1 (x y: Nat) (xs: List Nat): List.length (evens (x :: y :: xs)) < Nat.succ (Nat.succ (List.length xs)) := by sorry

x y : ℕ
xs : List ℕ
⊢ (evens (x :: y :: xs)).length < xs.length.succ.succ


theorem bsort_term2 (x y: Nat) (xs: List Nat): List.length (odds (x :: y :: xs)) < Nat.succ (Nat.succ (List.length xs)) := by sorry

x y : ℕ
xs : List ℕ
⊢ (odds (x :: y :: xs)).length < xs.length.succ.succ


theorem filter_len_le {f: Nat → Bool} {xs: List Nat}: (filter xs f).length <= xs.length := by sorry

import Mathlib

def filter : List Nat → (Nat → Bool) → List Nat
| [], _f => []
| x::xs, f => if f x then x::(filter xs f) else (filter xs f)

f : ℕ → Bool
xs : List ℕ
⊢ (filter xs f).length ≤ xs.length


theorem qsort_term (x:Nat) (xs: List Nat) : List.length (filter xs fun y => decide (y ≤ x)) < Nat.succ (List.length xs) := by sorry

x : ℕ
xs : List ℕ
⊢ (filter xs fun y => decide (y ≤ x)).length < xs.length.succ


theorem qsort_term2 (x:Nat) (xs: List Nat) : List.length (filter xs fun y => decide (y > x)) < Nat.succ (List.length xs) := by sorry

x : ℕ
xs : List ℕ
⊢ (filter xs fun y => decide (y > x)).length < xs.length.succ


theorem min_in_list : minimum x xs ∈ (x::xs) := by sorry

import Mathlib

def minimum : Nat → List Nat → Nat
| x, [] => x
| x, y::ys => if y <= x then minimum y ys else minimum x ys

x : ℕ
xs : List ℕ
⊢ minimum x xs ∈ x :: xs


theorem delete_len_eq {x: Nat} {xs: List Nat} (h: x ∈ xs): (deleteFirst x xs).length + 1 = xs.length := by sorry

import Mathlib

def deleteFirst [DecidableEq α]: α → List α → List α
  | _, [] => []
  | n, x::xs => if n == x then xs else x::(deleteFirst n xs)

x : ℕ
xs : List ℕ
h : x ∈ xs
⊢ (deleteFirst x xs).length + 1 = xs.length


theorem len_rev_eq_len {l: List Nat} : (reverse l).length = l.length := by sorry

import Mathlib

def reverse : List Nat → List Nat
| [] => []
| x::xs => (reverse xs) ++ [x]

l : List ℕ
⊢ (reverse l).length = l.length


theorem splitAt_len_le {xs: List Nat}: (splitAt n xs).2.length ≤ xs.length := by sorry

import Mathlib

def splitAt : Nat → List Nat → (List Nat × List Nat)
| _n, [] => ([], [])
| 0, xs => ([], xs)
| n + 1, x::xs => match splitAt n xs with
  | (l1, l2) => (x::l1, l2)

n : ℕ
xs : List ℕ
⊢ (splitAt n xs).2.length ≤ xs.length


theorem splitAt_second_len_lt (n: Nat): (splitAt n.succ (x::xs)).2.length < (x::xs).length := by sorry

x : ℕ
xs : List ℕ
n : ℕ
⊢ (splitAt n.succ (x :: xs)).2.length < (x :: xs).length


theorem splitAt_second_len_lt' {xs: List Nat} (n: Nat) (hlen: xs.length > 0): (splitAt n.succ xs).2.length < xs.length := by sorry

xs : List ℕ
n : ℕ
hlen : xs.length > 0
⊢ (splitAt n.succ xs).2.length < xs.length


theorem splitAt_second_len_lt'' {xs: List Nat} (hn: n > 0) (hlen: xs.length > 0) (hlen': xl = xs.length): (splitAt n xs).2.length < xl := by sorry

n xl : ℕ
xs : List ℕ
hn : n > 0
hlen : xs.length > 0
hlen' : xl = xs.length
⊢ (splitAt n xs).2.length < xl


theorem splitAt_sum_preserves_len (n: Nat) (xs: List Nat) (hspl: spl = splitAt n xs): (spl.1.length + spl.2.length = xs.length) := by sorry

spl : List ℕ × List ℕ
n : ℕ
xs : List ℕ
hspl : spl = splitAt n xs
⊢ spl.1.length + spl.2.length = xs.length


theorem splitAt_first_len_lt {xs: List Nat} (hn: n < xl) (hlen': xl = xs.length): (splitAt n xs).1.length < xl := by sorry

n xl : ℕ
xs : List ℕ
hn : n < xl
hlen' : xl = xs.length
⊢ (splitAt n xs).1.length < xl


theorem twon_lt (n: Nat): (2*n.succ.succ.succ + 1)/ 3 < n.succ.succ.succ := by sorry

n : ℕ
⊢ (2 * n.succ.succ.succ + 1) / 3 < n.succ.succ.succ


theorem third_eq_div_3 : (x/3) = third x := by sorry

import Mathlib

def third : Nat → Nat
| 0 => 0
| 1 => 0
| 2 => 0
| n + 3 => 1 + (third n)


theorem twon_lt' (n: Nat): twoThirds (n.succ.succ.succ) < n.succ.succ.succ := by sorry

import Mathlib

def twoThirds : Nat → Nat
| 0 => 0
| 1 => 0
| 2 => 0
| n + 3 => 2 + (twoThirds n)

n : ℕ
⊢ twoThirds n.succ.succ.succ < n.succ.succ.succ