Imports
import Mathlib18.1. B-Tree Model
Defines the B-tree data type, key membership, and the full structural invariants
(Sorted, ChildBounded, Occupancy, SameDepth). Proves that the
B-TREE-SPLIT-CHILD operation preserves every invariant:
splitChild_preserves_sorted, splitChild_preserves_childBounded,
splitChild_preserves_occupancy, and splitChild_preserves_sameDepth, combined
into splitChild_preserves_wellFormed (all with 0 sorry).
Implementation details
namespace CLRSnamespace Chapter18inductive BTree where
| node (keys : List Nat) (children : List BTree) : BTree
deriving Reprnamespace BTreeopen ListKeys and membership
def keysOf : BTree -> List Nat
| node keys children => keys ++ children.flatMap keysOfdef mem (x : Nat) (t : BTree) : Prop := x ∈ keysOf tNo represented key occurs more than once anywhere in the tree.
instance decidableMem (x : Nat) (t : BTree) : Decidable (mem x t) :=
inferInstanceAs (Decidable (x ∈ keysOf t))def Valid (minDegree : Nat) (_t : BTree) : Prop := 2 <= minDegreedef search (x : Nat) (t : BTree) : Bool := decide (mem x t)theorem search_true_iff (x : Nat) (t : BTree) :
search x t = true ↔ mem x t := by simp [search]theorem search_true_of_mem (x : Nat) (t : BTree) (hx : mem x t) :
search x t = true := (search_true_iff x t).mpr hxtheorem mem_of_search_true (x : Nat) (t : BTree) (hx : search x t = true) :
mem x t := (search_true_iff x t).mp hxtheorem search_false_iff (x : Nat) (t : BTree) :
search x t = false ↔ ¬ mem x t := by simp [search]theorem search_false_of_not_mem (x : Nat) (t : BTree) (hx : ¬ mem x t) :
search x t = false := (search_false_iff x t).mpr hxtheorem not_mem_of_search_false (x : Nat) (t : BTree) (hx : search x t = false) :
¬ mem x t := (search_false_iff x t).mp hxtheorem search_correct {minDegree x : Nat} {t : BTree}
(_hvalid : Valid minDegree t) : search x t = true ↔ mem x t :=
search_true_iff x tMinimum-key lower bound expression
def minKeys (minDegree height : Nat) : Nat := 2 * minDegree ^ height - 1theorem minKeys_zero (minDegree : Nat) : minKeys minDegree 0 = 1 := by simp [minKeys]
theorem minKeys_pos {minDegree height : Nat} (hdegree : 0 < minDegree) :
0 < minKeys minDegree height := by
unfold minKeys
have hpow : 0 < minDegree ^ height := pow_pos hdegree height
have hlt : 1 < 2 * minDegree ^ height := by omega
exact Nat.sub_pos_of_lt hlttheorem one_le_minKeys {minDegree height : Nat} (hdegree : 0 < minDegree) :
1 <= minKeys minDegree height := Nat.succ_le_of_lt (minKeys_pos hdegree)theorem minKeys_lower_bound {minDegree height : Nat} (_hdegree : 2 <= minDegree) :
2 * minDegree ^ height - 1 <= minKeys minDegree height := by rfl
theorem minKeys_succ {minDegree height : Nat} (hdegree : 2 <= minDegree) :
minKeys minDegree (height + 1) + 1 = minDegree * (minKeys minDegree height + 1) := by
unfold minKeys; have hpos : 0 < minDegree := by omega
have hpowPos : 0 < minDegree ^ height := pow_pos hpos height
have hnextPowPos : 0 < minDegree ^ (height + 1) := pow_pos hpos (height + 1)
have hnextTermPos : 0 < 2 * minDegree ^ (height + 1) := Nat.mul_pos (by decide) hnextPowPos
have htermPos : 0 < 2 * minDegree ^ height := Nat.mul_pos (by decide) hpowPos
rw [Nat.sub_add_cancel (Nat.succ_le_of_lt hnextTermPos)]
rw [Nat.sub_add_cancel (Nat.succ_le_of_lt htermPos)]
rw [Nat.pow_succ]; ring
theorem minKeys_le_succ {minDegree height : Nat} (hdegree : 2 <= minDegree) :
minKeys minDegree height <= minKeys minDegree (height + 1) := by
unfold minKeys; have hpos : 0 < minDegree := by omega
have hpow : minDegree ^ height <= minDegree ^ (height + 1) := by
rw [Nat.pow_succ]; exact Nat.le_mul_of_pos_right _ hpos
exact Nat.sub_le_sub_right (Nat.mul_le_mul_left 2 hpow) 1theorem minKeys_monotone_height {minDegree h₁ h₂ : Nat}
(hdegree : 2 <= minDegree) (hheight : h₁ <= h₂) :
minKeys minDegree h₁ <= minKeys minDegree h₂ := by
induction hheight with | refl => rfl | step _ ih =>
exact Nat.le_trans ih (minKeys_le_succ hdegree)Structural invariants
def Sorted : BTree → Prop
| node keys children =>
List.Pairwise (· ≤ ·) keys ∧ ∀ child ∈ children, Sorted childdef ChildBounded : BTree → Prop
| node keys children =>
(children.isEmpty ∨ children.length = keys.length + 1) ∧
(∀ (i : Nat) (hi_child : i < children.length),
let child := children.get ⟨i, hi_child⟩
(i = 0 ∨ (match keys[i-1]? with
| some lo => ∀ k ∈ keysOf child, lo ≤ k
| none => True)) ∧
(match keys[i]? with
| some hi => ∀ k ∈ keysOf child, k ≤ hi
| none => True)) ∧
∀ child ∈ children, ChildBounded child
The B-tree occupancy bounds. A non-empty root has at least one key and an
internal root has at least two children; non-root nodes use the ordinary
minDegree - 1 key and minDegree child lower bounds.
def Occupancy (minDegree : Nat) (isRoot : Bool) : BTree → Prop
| node keys children =>
let lower := if isRoot then
(if keys.length = 0 ∧ children.isEmpty then 0 else 1) else minDegree - 1
let upper := 2 * minDegree - 1
let childLower := if isRoot then 2 else minDegree
lower ≤ keys.length ∧ keys.length ≤ upper ∧
(children.isEmpty ∨
(childLower ≤ children.length ∧ children.length ≤ 2 * minDegree)) ∧
∀ child ∈ children, Occupancy minDegree false childdef heightOf : BTree → Nat
| node _ [] => 0
| node _ cs => 1 + ((cs.map heightOf).foldl max 0)inductive SameDepth : BTree → Prop
| leaf (ks : List Nat) : SameDepth (node ks [])
| internal (ks : List Nat) (c0 : BTree) (cs : List BTree) :
(∀ c ∈ cs, heightOf c = heightOf c0) → SameDepth c0 → (∀ c ∈ cs, SameDepth c) →
SameDepth (node ks (c0 :: cs))def WellFormed (minDegree : Nat) (t : BTree) : Prop :=
Sorted t ∧ ChildBounded t ∧ Occupancy minDegree true t ∧ SameDepth tStructural B-tree well-formedness plus global key uniqueness.
def WellFormedUnique (t : Nat) (tr : BTree) : Prop :=
WellFormed t tr ∧ UniqueKeys trtheorem WellFormed.valid {minDegree : Nat} {t : BTree}
(hmin : 2 ≤ minDegree) (_h : WellFormed minDegree t) : Valid minDegree t := by
unfold Valid; exact hmintheorem wellFormed_empty (minDegree : Nat) (hmin : 2 ≤ minDegree) :
WellFormed minDegree (node [] []) := by
unfold WellFormed Sorted ChildBounded Occupancy
refine ⟨?_, ?_, ?_, SameDepth.leaf []⟩
· unfold Sorted; simp
· unfold ChildBounded; simp
· unfold Occupancy; simpB-TREE-SPLIT-CHILD operation
def splitChild (t : Nat) : BTree → Nat → BTree
| node keys children, i =>
if h : i < children.length then
match children.get ⟨i, h⟩ with
| node cKeys cChildren =>
if cKeys.length = 2 * t - 1 then
match cKeys.splitAt (t - 1), cChildren.splitAt t with
| (leftKeys, medianKey :: rightKeys), (leftCh, rightCh) =>
BTree.node (keys.take i ++ medianKey :: keys.drop i)
(children.take i ++ [BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
children.drop (i + 1))
| _, _ => node keys children
else
node keys children
else
node keys childrenOccupancy preservation under splitChild
lemma splitAt_first_half_length (cKeys : List Nat) (t : Nat) (hfull : cKeys.length = 2 * t - 1) :
(cKeys.splitAt (t - 1)).1.length = t - 1 := by
simp [hfull]; omega
lemma splitAt_second_half_length (cKeys : List Nat) (t : Nat)
(hfull : cKeys.length = 2 * t - 1) (ht : 1 ≤ t) :
((cKeys.splitAt (t - 1)).2.drop 1).length = t - 1 := by
have h_snd_len : (cKeys.splitAt (t - 1)).2.length = t := by
simp [hfull]; omega
simp [h_snd_len]; omega
theorem splitChild_new_children_key_counts (t : Nat) (ht : 2 ≤ t)
(cKeys : List Nat) (hfull : cKeys.length = 2 * t - 1) :
((cKeys.splitAt (t - 1)).1).length = t - 1 ∧
((cKeys.splitAt (t - 1)).2.drop 1).length = t - 1 := by
have ht_pos : 1 ≤ t := by omega
exact ⟨splitAt_first_half_length cKeys t hfull,
splitAt_second_half_length cKeys t hfull ht_pos⟩theorem splitChild_parent_key_bound (t : Nat) (ht : 2 ≤ t) (keys : List Nat)
(hparent_nonfull : keys.length < 2 * t - 1) :
keys.length + 1 ≤ 2 * t - 1 := by
omegaList utility: foldl max over uniform values
lemma foldl_max_idem (l : List Nat) (a : Nat) (h : ∀ b ∈ l, b = a) : foldl max a l = a := by
induction l with
| nil => simp
| cons x xs ih =>
have hx : x = a := h x (by simp)
have hxs : ∀ b ∈ xs, b = a := by
intro b hb; exact h b (by simp [hb])
rw [hx]
simp [ih hxs]
lemma foldl_max_eq_of_all_eq (l : List Nat) (v : Nat) (h_ne : l ≠ [])
(h : ∀ a ∈ l, a = v) : l.foldl max 0 = v := by
cases l with
| nil => contradiction
| cons x xs =>
have hx : x = v := h x (by simp)
have hxs : ∀ a ∈ xs, a = v := by
intro a ha; exact h a (by simp [ha])
rw [hx]
simp
exact foldl_max_idem xs v hxsSameDepth infrastructure and preservation
lemma sameDepth_children_eq_height {ks : List Nat} {c0 : BTree} {cs : List BTree}
(hsd : SameDepth (node ks (c0 :: cs))) :
∀ c₁ ∈ (c0 :: cs), ∀ c₂ ∈ (c0 :: cs), heightOf c₁ = heightOf c₂ := by
refine SameDepth.casesOn hsd
(motive := λ t _ => match t with
| node _ children => ∀ c₁ ∈ children, ∀ c₂ ∈ children, heightOf c₁ = heightOf c₂)
?leaf ?internal
· intro ks'; intro c₁ hc₁; simp at hc₁
· intro ks' c0' cs' h_heights _h_sd_c0' _h_sd_children'
intro c₁ hc₁ c₂ hc₂
simp at hc₁ hc₂
rcases hc₁ with (rfl | hc₁')
· rcases hc₂ with (rfl | hc₂')
· rfl
· symm; exact h_heights c₂ hc₂'
· rcases hc₂ with (rfl | hc₂')
· exact h_heights c₁ hc₁'
· rw [h_heights c₁ hc₁', h_heights c₂ hc₂']lemma sameDepth_head_sd {ks : List Nat} {c0 : BTree} {cs : List BTree}
(hsd : SameDepth (node ks (c0 :: cs))) : SameDepth c0 := by
refine SameDepth.casesOn hsd (motive := λ t _ => match t with
| node _ (c0' :: _) => SameDepth c0'
| node _ [] => True) ?leaf ?internal
· intro ks'; trivial
· intro ks' c0' cs' _ h_sd_c0' _; exact h_sd_c0'lemma sameDepth_tail_sd {ks : List Nat} {c0 : BTree} {cs : List BTree}
(hsd : SameDepth (node ks (c0 :: cs))) (c : BTree) (hc : c ∈ cs) : SameDepth c := by
refine SameDepth.casesOn hsd (motive := λ t _ => match t with
| node _ (c0' :: cs') => ∀ c' ∈ cs', SameDepth c'
| node _ [] => ∀ c' ∈ [], SameDepth c') ?leaf ?internal c hc
· intro ks' c' hc'; simp at hc'
· intro ks' c0' cs' _ _ h_sd_children'; exact h_sd_children'
lemma sameDepth_take (cKeys : List Nat) (cChildren : List BTree) (t : Nat)
(hsd : SameDepth (node cKeys cChildren)) (ht_pos : 1 ≤ t) :
SameDepth (node ((cKeys.splitAt (t - 1)).1) ((cChildren.splitAt t).1)) := by
cases cChildren with
| nil => simp; exact SameDepth.leaf _
| cons d0 ds =>
have h_take : ((d0 :: ds).splitAt t).1 = d0 :: (ds.take (t-1)) := by
cases t; omega; rename_i n; simp
rw [h_take]
have h_sd_d0 : SameDepth d0 := sameDepth_head_sd hsd
have h_sd_ds : ∀ d ∈ ds.take (t-1), SameDepth d := by
intro d hd
exact sameDepth_tail_sd hsd d ((take_sublist (t-1) ds).subset hd)
have h_heights : ∀ d ∈ ds.take (t-1), heightOf d = heightOf d0 := by
intro d hd
have hmem : d ∈ d0 :: ds := by
apply mem_cons_of_mem d0
exact (take_sublist (t-1) ds).subset hd
exact (sameDepth_children_eq_height hsd) d hmem d0 (by simp)
exact SameDepth.internal ((cKeys.splitAt (t - 1)).1) d0 (ds.take (t-1))
h_heights h_sd_d0 h_sd_ds
lemma sameDepth_drop (cKeys : List Nat) (cChildren : List BTree) (t : Nat)
(hsd : SameDepth (node cKeys cChildren)) (ht_pos : 1 ≤ t) :
SameDepth (node ((cKeys.splitAt (t - 1)).2.drop 1) ((cChildren.splitAt t).2)) := by
cases cChildren with
| nil => simp; exact SameDepth.leaf _
| cons d0 ds =>
have h_drop : ((d0 :: ds).splitAt t).2 = ds.drop (t-1) := by
cases t; omega; rename_i n; simp
rw [h_drop]
by_cases h_empty : ds.drop (t-1) = []
· simp [h_empty]; exact SameDepth.leaf _
· match h_drop_suffix : ds.drop (t-1) with
| [] => exact (h_empty h_drop_suffix).elim
| e0 :: es =>
have he0_mem_drop : e0 ∈ ds.drop (t-1) := by rw [h_drop_suffix]; simp
have he0_ds : e0 ∈ ds := (drop_sublist (t-1) ds).subset he0_mem_drop
have h_sd_e0 : SameDepth e0 := sameDepth_tail_sd hsd e0 he0_ds
have h_sd_es : ∀ e ∈ es, SameDepth e := by
intro e he
have he_mem_drop : e ∈ ds.drop (t-1) := by rw [h_drop_suffix]; simp [he]
have he_ds : e ∈ ds := (drop_sublist (t-1) ds).subset he_mem_drop
exact sameDepth_tail_sd hsd e he_ds
have h_heights : ∀ e ∈ es, heightOf e = heightOf e0 := by
intro e he
have he_mem_drop : e ∈ ds.drop (t-1) := by rw [h_drop_suffix]; simp [he]
have he_ds : e ∈ ds := (drop_sublist (t-1) ds).subset he_mem_drop
have he0_cons : e0 ∈ d0 :: ds := by simp [he0_ds]
have he_cons : e ∈ d0 :: ds := by simp [he_ds]
exact (sameDepth_children_eq_height hsd) e he_cons e0 he0_cons
refine SameDepth.internal ((cKeys.splitAt (t - 1)).2.drop 1) e0 es
h_heights h_sd_e0 h_sd_esHeight of a SameDepth internal node
lemma heightOf_uniform_children {ks : List Nat} {c0 : BTree} {cs : List BTree}
(h : ∀ c ∈ cs, heightOf c = heightOf c0) :
heightOf (node ks (c0 :: cs)) = 1 + heightOf c0 := by
simp [heightOf]
refine (Nat.succ_inj).mp ?_
simp
refine foldl_max_idem (List.map heightOf cs) (heightOf c0) ?_
intro x hx
rw [List.mem_map] at hx
rcases hx with ⟨c, hc, rfl⟩
exact h c hclemma heightOf_internal_of_sameDepth {ks : List Nat} {c0 : BTree} {cs : List BTree}
(hsd : SameDepth (node ks (c0 :: cs))) : heightOf (node ks (c0 :: cs)) = 1 + heightOf c0 := by
match hsd with
| SameDepth.internal ks' c0' cs' h_heights _ _ =>
exact heightOf_uniform_children h_heights
lemma heightOf_split_parts_eq (cKeys : List Nat) (cChildren : List BTree) (t : Nat)
(hsd : SameDepth (node cKeys cChildren))
(ht_pos : 0 < t)
(h_children : cChildren = [] ∨ t < cChildren.length) :
heightOf (node ((cKeys.splitAt (t - 1)).1) ((cChildren.splitAt t).1)) =
heightOf (node cKeys cChildren) ∧
heightOf (node ((cKeys.splitAt (t - 1)).2.drop 1) ((cChildren.splitAt t).2)) =
heightOf (node cKeys cChildren) := by
rcases h_children with (h_empty | h_gt)
· subst h_empty; simp [heightOf]
· have h_nonempty : cChildren ≠ [] := by
intro h; rw [h] at h_gt; simp at h_gt
cases h_cases : cChildren with
| nil => exact (h_nonempty h_cases).elim
| cons d0 ds =>
have hsd_internal : heightOf (node cKeys (d0 :: ds)) = 1 + heightOf d0 :=
heightOf_internal_of_sameDepth (by rwa [h_cases] at hsd)
have h_all_eq : ∀ c₁ ∈ (d0 :: ds), ∀ c₂ ∈ (d0 :: ds), heightOf c₁ = heightOf c₂ :=
sameDepth_children_eq_height (by rwa [h_cases] at hsd)
have h_take_head : ((d0 :: ds).splitAt t).1 = d0 :: (ds.take (t - 1)) := by
cases t; omega; rename_i n; simp
rw [h_take_head]
have h_left_heights : ∀ c ∈ ds.take (t - 1), heightOf c = heightOf d0 := by
intro c hc
have hc_mem : c ∈ d0 :: ds :=
List.mem_cons_of_mem _ ((List.take_sublist (t - 1) ds).subset hc)
exact h_all_eq c hc_mem d0 (by simp)
have h_left_height : heightOf (node ((cKeys.splitAt (t - 1)).1) (d0 :: ds.take (t - 1))) =
1 + heightOf d0 :=
heightOf_uniform_children h_left_heights
have h_drop_eq : ((d0 :: ds).splitAt t).2 = ds.drop (t - 1) := by
cases t; omega; rename_i n; simp
rw [h_drop_eq]
have h_right_nonempty : ds.drop (t - 1) ≠ [] := by
have hlen_cons : t < (d0 :: ds).length := by simpa [h_cases] using h_gt
intro h
have hlen0 : (ds.drop (t - 1)).length = 0 := by simpa [h]
rw [List.length_drop] at hlen0
have : ds.length ≤ t - 1 := by omega
have : ds.length + 1 ≤ t := by omega
simp at hlen_cons
omega
match h_drop_suffix : ds.drop (t - 1) with
| nil => exact (h_right_nonempty h_drop_suffix).elim
| cons e0 es =>
have h_right_heights : ∀ c ∈ es, heightOf c = heightOf e0 := by
intro c hc
have hc_mem : c ∈ d0 :: ds := by
apply List.mem_cons_of_mem _
have hmem_drop : c ∈ ds.drop (t - 1) := by rw [h_drop_suffix]; simp [hc]
exact (List.drop_sublist (t - 1) ds).subset hmem_drop
have he0_mem : e0 ∈ d0 :: ds := by
apply List.mem_cons_of_mem _
have he0_drop : e0 ∈ ds.drop (t - 1) := by rw [h_drop_suffix]; simp
exact (List.drop_sublist (t - 1) ds).subset he0_drop
exact h_all_eq c hc_mem e0 he0_mem
have h_right_height : heightOf (node ((cKeys.splitAt (t - 1)).2.drop 1) (e0 :: es)) =
1 + heightOf e0 :=
heightOf_uniform_children h_right_heights
have h_d0_e0_height : heightOf e0 = heightOf d0 := by
have he0_mem : e0 ∈ d0 :: ds := by
apply List.mem_cons_of_mem _
have he0_drop : e0 ∈ ds.drop (t - 1) := by rw [h_drop_suffix]; simp
exact (List.drop_sublist (t - 1) ds).subset he0_drop
exact h_all_eq e0 he0_mem d0 (by simp)
rw [h_d0_e0_height] at h_right_height
rw [h_left_height, h_right_height, hsd_internal]
exact ⟨rfl, rfl⟩
theorem splitChild_preserves_sameDepth (t : Nat) (ht : 2 ≤ t)
(keys : List Nat) (children : List BTree)
(cKeys : List Nat) (cChildren : List BTree) (i : Nat)
(h_lt : i < children.length)
(hchild_eq : children.get ⟨i, h_lt⟩ = node cKeys cChildren)
(hchild_full : cKeys.length = 2 * t - 1)
(hchild_children : cChildren = [] ∨ t < cChildren.length)
(hsd : SameDepth (node keys children)) :
SameDepth (splitChild t (node keys children) i) := by
have ht_pos : 1 ≤ t := by omega
have ht_pos' : 0 < t := by omega
have h_keys_snd_nonempty : (cKeys.splitAt (t - 1)).2 ≠ [] := by
have hlen : (cKeys.splitAt (t - 1)).2.length = t := by simp [hchild_full]; omega
intro h; rw [h] at hlen; simp at hlen; omega
dsimp [splitChild]
rw [dif_pos h_lt]
have h_get : children[i] = node cKeys cChildren := by simpa using hchild_eq
rw [h_get]
dsimp
rw [if_pos hchild_full]
cases hk : cKeys.splitAt (t - 1) with
| mk leftKeys keysRest =>
have h_keysRest_nonempty : keysRest ≠ [] := by
have : (cKeys.splitAt (t - 1)).2 = keysRest := by rw [hk]
rw [← this]; exact h_keys_snd_nonempty
cases hkr : keysRest with
| nil => exact (h_keysRest_nonempty hkr).elim
| cons medianKey rightKeys =>
cases hc : cChildren.splitAt t with
| mk leftCh rightCh =>
-- The match reduces to the success branch
show SameDepth (BTree.node (take i keys ++ medianKey :: drop i keys)
(take i children ++ [BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i + 1) children))
cases hsd with
| leaf ks => simp at h_lt
| internal ks c0 cs h_heights h_sd_c0 h_sd_cs =>
have h_sd_child : SameDepth (node cKeys cChildren) := by
rcases Nat.eq_zero_or_pos i with (rfl | hi_pos')
· have hc0_eq : c0 = node cKeys cChildren := by simpa using hchild_eq
rw [← hc0_eq]; exact h_sd_c0
· have h_get' : (c0 :: cs).get ⟨i, h_lt⟩ = cs.get ⟨i - 1, by
simp at h_lt; omega⟩ := by
rcases i with (rfl | i)
· exact (Nat.not_lt_zero _ hi_pos').elim
· simp
have hmem : cs.get ⟨i - 1, by simp at h_lt; omega⟩ ∈ cs := by
apply List.get_mem
rw [← hchild_eq, h_get']
exact h_sd_cs _ hmem
have h_keys_left : ((cKeys.splitAt (t - 1)).1) = leftKeys := by rw [hk]
have h_keys_right : ((cKeys.splitAt (t - 1)).2.drop 1) = rightKeys := by
rw [hk]; simp [hkr]
have h_ch_left : ((cChildren.splitAt t).1) = leftCh := by rw [hc]
have h_ch_right : ((cChildren.splitAt t).2) = rightCh := by rw [hc]
have h_sd_left : SameDepth (node leftKeys leftCh) := by
rw [← h_keys_left, ← h_ch_left]; exact sameDepth_take cKeys cChildren t h_sd_child ht_pos
have h_sd_right : SameDepth (node rightKeys rightCh) := by
rw [← h_keys_right, ← h_ch_right]; exact sameDepth_drop cKeys cChildren t h_sd_child ht_pos
have h_heights_split := heightOf_split_parts_eq cKeys cChildren t h_sd_child ht_pos' hchild_children
have h_height_left : heightOf (node leftKeys leftCh) = heightOf (node cKeys cChildren) := by
rw [← h_keys_left, ← h_ch_left]; exact h_heights_split.1
have h_height_right : heightOf (node rightKeys rightCh) = heightOf (node cKeys cChildren) := by
rw [← h_keys_right, ← h_ch_right]; exact h_heights_split.2
have h_child_eq_c0_height : heightOf (node cKeys cChildren) = heightOf c0 := by
rcases Nat.eq_zero_or_pos i with (rfl | hi_pos')
· have hc0_eq : c0 = node cKeys cChildren := by simpa using hchild_eq
rw [← hc0_eq]
· have h_get' : (c0 :: cs).get ⟨i, h_lt⟩ = cs.get ⟨i - 1, by
simp at h_lt; omega⟩ := by
rcases i with (rfl | i)
· exact (Nat.not_lt_zero _ hi_pos').elim
· simp
have hmem : cs.get ⟨i - 1, by simp at h_lt; omega⟩ ∈ cs := by
apply List.get_mem
rw [← hchild_eq, h_get']
exact h_heights _ hmem
rcases Nat.eq_zero_or_pos i with (rfl | hi_pos)
· -- i = 0: result children = newLeft :: newRight :: cs
have h_rest_heights : ∀ c ∈ (node rightKeys rightCh :: cs),
heightOf c = heightOf (node leftKeys leftCh) := by
intro c hc; simp at hc; rcases hc with (rfl | hc_cs)
· rw [h_height_right, h_height_left]
· rw [h_heights c hc_cs, ← h_child_eq_c0_height, h_height_left]
have h_rest_sd : ∀ c ∈ (node rightKeys rightCh :: cs), SameDepth c := by
intro c hc; simp at hc; rcases hc with (rfl | hc_cs)
· exact h_sd_right
· exact h_sd_cs c hc_cs
refine SameDepth.internal (take 0 keys ++ medianKey :: drop 0 keys)
(node leftKeys leftCh) (node rightKeys rightCh :: cs) h_rest_heights h_sd_left h_rest_sd
· -- i > 0: result children = c0 :: take(i-1)cs ++ left :: right :: drop i cs
have h_take : take i (c0 :: cs) = c0 :: take (i - 1) cs := by
rcases i with (rfl | i)
· exact (Nat.not_lt_zero _ hi_pos).elim
· simp
have h_drop_succ : drop (i + 1) (c0 :: cs) = drop i cs := by simp
rw [h_take, h_drop_succ]
simp only [List.cons_append, List.append_assoc, List.nil_append]
have h_rest_heights : ∀ c ∈ (take (i - 1) cs ++ (node leftKeys leftCh :: node rightKeys rightCh :: drop i cs)),
heightOf c = heightOf c0 := by
intro c hc
rw [List.mem_append] at hc
rcases hc with (hc | hc)
· have hmem : c ∈ cs := (List.take_sublist _ _).subset hc
exact h_heights c hmem
· simp at hc; rcases hc with (rfl | rfl | hc)
· rw [h_height_left, h_child_eq_c0_height]
· rw [h_height_right, h_child_eq_c0_height]
· have hmem : c ∈ cs := (List.drop_sublist _ _).subset hc
exact h_heights c hmem
have h_rest_sd : ∀ c ∈ (take (i - 1) cs ++ (node leftKeys leftCh :: node rightKeys rightCh :: drop i cs)),
SameDepth c := by
intro c hc
rw [List.mem_append] at hc
rcases hc with (hc | hc)
· have hmem : c ∈ cs := (List.take_sublist _ _).subset hc
exact h_sd_cs c hmem
· simp at hc; rcases hc with (rfl | rfl | hc)
· exact h_sd_left
· exact h_sd_right
· have hmem : c ∈ cs := (List.drop_sublist _ _).subset hc
exact h_sd_cs c hmem
refine SameDepth.internal (take i keys ++ medianKey :: drop i keys) c0
(take (i - 1) cs ++ (node leftKeys leftCh :: node rightKeys rightCh :: drop i cs))
h_rest_heights h_sd_c0 h_rest_sdsplitChild occupancy preservation (stub)
The following theorem states that splitChild preserves the Occupancy
invariant. The proof requires:
-
Arithmetic showing that the two new children have
t-1keys each (fromsplitAt_first_half_length/splitAt_second_half_length) -
Arithmetic showing that children counts stay within
[t, 2t](requiresChildBoundedto knowcChildren.length = 2twhen non-empty) -
Propagation of sub-node occupancy from the original child.
-- Helper: extract child occupancy from parent occupancy
lemma occupancy_of_child {minDegree : Nat} {isRoot : Bool} {keys : List Nat} {children : List BTree}
(h_occ : Occupancy minDegree isRoot (node keys children))
(i : Nat) (hi : i < children.length) :
Occupancy minDegree false (children.get ⟨i, hi⟩) := by
unfold Occupancy at h_occ
rcases h_occ with ⟨_, _, _, h_sub⟩
apply h_sub
apply List.get_mem
-- Helper: from ChildBounded of a full node, children length is 0 or 2t
lemma child_children_len_of_full_cb {t : Nat} (ht : 2 ≤ t) {cKeys : List Nat} {cChildren : List BTree}
(h_cb : ChildBounded (node cKeys cChildren)) (h_full : cKeys.length = 2 * t - 1) :
cChildren.length = 0 ∨ cChildren.length = 2 * t := by
unfold ChildBounded at h_cb
rcases h_cb with ⟨h_rel, _, _⟩
rcases h_rel with (h_empty | h_eq)
· left; cases cChildren with | nil => rfl | cons x xs => simp at h_empty
· right; rw [h_eq, h_full]; omega
theorem splitChild_preserves_occupancy (t : Nat) (ht : 2 ≤ t)
(keys : List Nat) (children : List BTree)
(cKeys : List Nat) (cChildren : List BTree) (i : Nat)
(h_lt : i < children.length)
(hchild_eq : children.get ⟨i, h_lt⟩ = node cKeys cChildren)
(hchild_full : cKeys.length = 2 * t - 1)
(hparent_nonfull : keys.length < 2 * t - 1)
(h_occ : Occupancy t true (node keys children))
(h_cb : ChildBounded (node keys children)) :
Occupancy t true (splitChild t (node keys children) i) := by
have ht_pos : 0 < t := by omega
have ht_pos' : 1 ≤ t := by omega
-- Extract child invariants
have hchild_occ : Occupancy t false (node cKeys cChildren) := by
rw [← hchild_eq]; exact occupancy_of_child h_occ i h_lt
have hchild_cb : ChildBounded (node cKeys cChildren) := by
rw [← hchild_eq]; unfold ChildBounded at h_cb
rcases h_cb with ⟨_, _, h_sub⟩; apply h_sub; apply List.get_mem
have h_cChildren_len := child_children_len_of_full_cb ht hchild_cb hchild_full
-- Unfold splitChild (same pattern as splitChild_preserves_sameDepth)
have h_keys_snd_nonempty : (cKeys.splitAt (t - 1)).2 ≠ [] := by
have hlen : (cKeys.splitAt (t - 1)).2.length = t := by simp [hchild_full]; omega
intro h; rw [h] at hlen; simp at hlen; omega
dsimp [splitChild]; rw [dif_pos h_lt]
have h_get : children[i] = node cKeys cChildren := by simpa using hchild_eq
rw [h_get]; dsimp; rw [if_pos hchild_full]
cases hk : cKeys.splitAt (t - 1) with
| mk leftKeys keysRest =>
have h_keysRest_nonempty : keysRest ≠ [] := by
have : (cKeys.splitAt (t - 1)).2 = keysRest := by rw [hk]
rw [← this]; exact h_keys_snd_nonempty
cases hkr : keysRest with
| nil => exact (h_keysRest_nonempty hkr).elim
| cons medianKey rightKeys =>
cases hc : cChildren.splitAt t with
| mk leftCh rightCh =>
show Occupancy t true (BTree.node (take i keys ++ medianKey :: drop i keys)
(take i children ++ [BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i + 1) children))
-- Relate local names to splitAt results (matching SameDepth proof pattern)
have h_keys_left : ((cKeys.splitAt (t - 1)).1) = leftKeys := by rw [hk]
have h_keys_right : ((cKeys.splitAt (t - 1)).2.drop 1) = rightKeys := by
rw [hk]; simp [hkr]
have h_ch_left : ((cChildren.splitAt t).1) = leftCh := by rw [hc]
have h_ch_right : ((cChildren.splitAt t).2) = rightCh := by rw [hc]
-- Key length facts (using ← to apply splitAt lemmas)
have h_leftKeys_len : leftKeys.length = t - 1 := by
rw [← h_keys_left]; exact splitAt_first_half_length cKeys t hchild_full
have h_rightKeys_len : rightKeys.length = t - 1 := by
rw [← h_keys_right]; exact splitAt_second_half_length cKeys t hchild_full ht_pos'
-- Children count bounds for the two new children
have h_leftCh_bound : leftCh.isEmpty ∨ (t ≤ leftCh.length ∧ leftCh.length ≤ 2 * t) := by
rcases h_cChildren_len with (h0 | h2t)
· -- cChildren.length = 0 → cChildren = [] → leftCh = []
have hnil : cChildren = [] := by
cases cChildren with | nil => rfl | cons x xs => simp at h0
left; rw [← h_ch_left, hnil]; simp
· -- cChildren.length = 2t → leftCh.length = t
right; rw [← h_ch_left]; simp [h2t]; omega
have h_rightCh_bound : rightCh.isEmpty ∨ (t ≤ rightCh.length ∧ rightCh.length ≤ 2 * t) := by
rcases h_cChildren_len with (h0 | h2t)
· have hnil : cChildren = [] := by
cases cChildren with | nil => rfl | cons x xs => simp at h0
left; rw [← h_ch_right, hnil]; simp
· right; rw [← h_ch_right]; simp [h2t]; omega
-- Occupancy for the two new children (non-root)
have h_occ_left : Occupancy t false (BTree.node leftKeys leftCh) := by
unfold Occupancy
refine ⟨?_, ?_, h_leftCh_bound, ?_⟩
· rw [h_leftKeys_len]; exact le_rfl
· rw [h_leftKeys_len]; omega
· intro child hchild
rw [← h_ch_left] at hchild; simp at hchild
have : child ∈ cChildren :=
(take_sublist t cChildren).subset hchild
unfold Occupancy at hchild_occ
rcases hchild_occ with ⟨_, _, _, h_occ_sub⟩
exact h_occ_sub child this
have h_occ_right : Occupancy t false (BTree.node rightKeys rightCh) := by
unfold Occupancy
refine ⟨?_, ?_, h_rightCh_bound, ?_⟩
· rw [h_rightKeys_len]; exact le_rfl
· rw [h_rightKeys_len]; omega
· intro child hchild
rw [← h_ch_right] at hchild; simp at hchild
have : child ∈ cChildren :=
(drop_sublist t cChildren).subset hchild
unfold Occupancy at hchild_occ
rcases hchild_occ with ⟨_, _, _, h_occ_sub⟩
exact h_occ_sub child this
-- Parent occupancy after split: prove the four conjuncts
-- Derive i ≤ keys.length from ChildBounded and h_lt
have h_i_le_keys : i ≤ keys.length := by
unfold ChildBounded at h_cb; rcases h_cb with ⟨h_cb_rel, _, _⟩
rcases h_cb_rel with (h_cb_empty | h_cb_eq)
· have h_len0 : children.length = 0 := by simpa using h_cb_empty
have : i < 0 := by rwa [h_len0] at h_lt
omega
· rw [h_cb_eq] at h_lt; omega
unfold Occupancy
have h_newKeys_len : (take i keys ++ medianKey :: drop i keys).length = keys.length + 1 := by
simp [h_i_le_keys]; omega
have h_newChildren_len : (take i children ++
[BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i + 1) children).length = children.length + 1 := by
simp; omega
refine ⟨?_, ?_, ?_, ?_⟩
· -- lower bound: the newKeys list is non-empty (contains medianKey)
have h_ne_nil : take i keys ++ medianKey :: drop i keys ≠ [] := by simp
have h_pos : 0 < (take i keys ++ medianKey :: drop i keys).length := by omega
have h_one_le : 1 ≤ (take i keys ++ medianKey :: drop i keys).length := by omega
have h_if_val : (if (take i keys ++ medianKey :: drop i keys).length = 0 ∧
(take i children ++ [BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i+1) children).isEmpty then 0 else 1) = 1 := by
by_cases hzero : (take i keys ++ medianKey :: drop i keys).length = 0
· exfalso; exact h_pos.ne' hzero
· simp [hzero]
rw [h_if_val]; exact h_one_le
· -- newKeys.length ≤ 2t-1 (parent was not full, added 1 key)
rw [h_newKeys_len]; omega
· -- children count: newChildren non-empty, length = children.length + 1
rw [h_newChildren_len]; right
have h_low : 2 ≤ children.length + 1 := by
omega
have h_high : children.length + 1 ≤ 2 * t := by
unfold ChildBounded at h_cb; rcases h_cb with ⟨h_cb_rel, _, _⟩
rcases h_cb_rel with (h_cb_empty | h_cb_eq)
· have h_len0 : children.length = 0 := by simpa using h_cb_empty
rw [h_len0]; omega
· rw [h_cb_eq]
have h_add := Nat.add_lt_add_right hparent_nonfull 1
rw [Nat.sub_add_cancel (show 1 ≤ 2 * t from by omega)] at h_add
rw [← Nat.succ_eq_add_one (keys.length + 1)]
exact Nat.succ_le_of_lt h_add
exact ⟨h_low, h_high⟩
· -- sub-node occupancy propagation
-- newChildren = (take i children) ++ [newLeft, newRight] ++ (drop (i+1) children)
-- Due to ++ associativity: (take ++ [a,b]) ++ drop
intro child hchild
have h_or := List.mem_append.mp hchild
rcases h_or with (h_take_or_new | h_drop)
· -- child ∈ take i children ++ [newLeft, newRight]
have h_or2 := List.mem_append.mp h_take_or_new
rcases h_or2 with (h_take | h_new)
· -- child ∈ take i children → inherits from parent occupancy
have hmem : child ∈ children := (take_sublist i children).subset h_take
unfold Occupancy at h_occ; rcases h_occ with ⟨_, _, _, h_pocc_sub⟩
exact h_pocc_sub child hmem
· -- child ∈ [newLeft, newRight]
simp at h_new; rcases h_new with (rfl | rfl)
· exact h_occ_left
· exact h_occ_right
· -- child ∈ drop (i+1) children → inherits from parent occupancy
have hmem : child ∈ children := (drop_sublist (i+1) children).subset h_drop
unfold Occupancy at h_occ; rcases h_occ with ⟨_, _, _, h_pocc_sub⟩
exact h_pocc_sub child hmem
lemma pairwise_get_mono {l : List Nat} (hp : List.Pairwise (· ≤ ·) l) {j k : Nat}
(hjk : j ≤ k) (hj : j < l.length) (hk : k < l.length) : l.get ⟨j, hj⟩ ≤ l.get ⟨k, hk⟩ := by
induction' hp with a l' h_all hp_tail ih generalizing j k
· exfalso; exact Nat.not_lt_zero j hj
· rcases k with (rfl | k)
· have hj0 : j = 0 := Nat.eq_zero_of_le_zero hjk
subst hj0; exact Nat.le_refl _
· have hk_lt : k < l'.length := by
have : k+1 < (a :: l').length := hk; simpa using this
rcases j with (rfl | j)
· simp; apply h_all; apply List.get_mem
· have hj_lt : j < l'.length := by
have : j+1 < (a :: l').length := hj; simpa using this
simp; apply ih (by omega) hj_lt hk_lt
theorem splitChild_preserves_sorted (t : Nat) (ht : 2 ≤ t)
(keys : List Nat) (children : List BTree)
(cKeys : List Nat) (cChildren : List BTree) (i : Nat)
(h_lt : i < children.length)
(hchild_eq : children.get ⟨i, h_lt⟩ = node cKeys cChildren)
(hchild_full : cKeys.length = 2 * t - 1)
(h_sorted : Sorted (node keys children))
(h_cb : ChildBounded (node keys children)) :
Sorted (splitChild t (node keys children) i) := by
have h_keys_snd_nonempty : (cKeys.splitAt (t - 1)).2 ≠ [] := by
have hlen : (cKeys.splitAt (t - 1)).2.length = t := by simp [hchild_full]; omega
intro h; rw [h] at hlen; simp at hlen; omega
dsimp [splitChild]; rw [dif_pos h_lt]
have h_get : children[i] = node cKeys cChildren := by simpa using hchild_eq
rw [h_get]; dsimp; rw [if_pos hchild_full]
cases hk : cKeys.splitAt (t - 1) with
| mk leftKeys keysRest =>
have h_keysRest_nonempty : keysRest ≠ [] := by
have : (cKeys.splitAt (t - 1)).2 = keysRest := by rw [hk]
rw [← this]; exact h_keys_snd_nonempty
cases hkr : keysRest with
| nil => exact (h_keysRest_nonempty hkr).elim
| cons medianKey rightKeys =>
cases hc : cChildren.splitAt t with
| mk leftCh rightCh =>
show Sorted (BTree.node (take i keys ++ medianKey :: drop i keys)
(take i children ++ [BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i + 1) children))
unfold Sorted at h_sorted; rcases h_sorted with ⟨h_keys_pairwise, h_children_sorted⟩
have hchild_sorted : Sorted (BTree.node cKeys cChildren) := by
rw [← hchild_eq]; apply h_children_sorted; apply List.get_mem
unfold Sorted at hchild_sorted
rcases hchild_sorted with ⟨h_cKeys_pairwise, h_cChildren_sorted⟩
-- Children sorted: same pattern as occupancy sub-node proof
have h_newChildren_sorted : ∀ child ∈ (take i children ++
[BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i + 1) children), Sorted child := by
intro child hchild
have h_or := List.mem_append.mp hchild
rcases h_or with (h_take_or_new | h_drop)
· have h_or2 := List.mem_append.mp h_take_or_new
rcases h_or2 with (h_take | h_new)
· have hmem : child ∈ children := (take_sublist i children).subset h_take
exact h_children_sorted child hmem
· simp at h_new; rcases h_new with (rfl | rfl)
· unfold Sorted
have h_lk : leftKeys = cKeys.take (t-1) := by
calc
leftKeys = (cKeys.splitAt (t-1)).1 := by rw [hk]
_ = cKeys.take (t-1) := by simp
have h_left_pairwise : List.Pairwise (· ≤ ·) leftKeys := by
rw [h_lk]; exact List.Pairwise.take (i := t-1) h_cKeys_pairwise
refine ⟨h_left_pairwise, ?_⟩
intro c hc_mem
have h_left_eq : leftCh = cChildren.take t := by
calc
leftCh = (cChildren.splitAt t).1 := by rw [hc]
_ = cChildren.take t := by simp
rw [h_left_eq] at hc_mem
apply h_cChildren_sorted
exact (take_sublist t cChildren).subset hc_mem
· unfold Sorted
have h_rk : rightKeys = cKeys.drop t := by
calc
rightKeys = keysRest.drop 1 := by rw [hkr]; simp
_ = (cKeys.splitAt (t-1)).2.drop 1 := by rw [hk]
_ = (cKeys.drop (t-1)).drop 1 := by simp
_ = cKeys.drop ((t-1)+1) := by rw [← List.drop_drop]
_ = cKeys.drop t := by rw [show (t-1)+1 = t by omega]
have h_right_pairwise : List.Pairwise (· ≤ ·) rightKeys := by
rw [h_rk]; exact List.Pairwise.drop (i := t) h_cKeys_pairwise
refine ⟨h_right_pairwise, ?_⟩
intro c hc_mem
have h_right_eq : rightCh = cChildren.drop t := by
calc
rightCh = (cChildren.splitAt t).2 := by rw [hc]
_ = cChildren.drop t := by simp
rw [h_right_eq] at hc_mem
apply h_cChildren_sorted
exact (drop_sublist t cChildren).subset hc_mem
· have hmem : child ∈ children := (drop_sublist (i+1) children).subset h_drop
exact h_children_sorted child hmem
-- Keys pairwise: proved using pairwise_get_mono + ChildBounded bounds + pairwise_append.
have h_keys_ok : List.Pairwise (· ≤ ·) (take i keys ++ medianKey :: drop i keys) := by
-- pairwise properties of the two parts
have h_take_pw : List.Pairwise (· ≤ ·) (take i keys) :=
List.Pairwise.take (i := i) h_keys_pairwise
have h_drop_pw : List.Pairwise (· ≤ ·) (drop i keys) :=
List.Pairwise.drop (i := i) h_keys_pairwise
-- cross-bound from original pairwise
have h_keys_eq : take i keys ++ drop i keys = keys := by simp
have h_pw_app : List.Pairwise (· ≤ ·) (take i keys ++ drop i keys) := by
rw [h_keys_eq]; exact h_keys_pairwise
have h_full := (List.pairwise_append (l₁ := take i keys) (l₂ := drop i keys)).mp h_pw_app
rcases h_full with ⟨_, _, h_cross⟩
-- medianKey is in cKeys (from the split)
have h_median_in_cKeys : medianKey ∈ cKeys := by
have h_cKeys_eq : cKeys = leftKeys ++ medianKey :: rightKeys := by
calc
cKeys = cKeys.take (t-1) ++ cKeys.drop (t-1) := by simp
_ = (cKeys.splitAt (t-1)).1 ++ (cKeys.splitAt (t-1)).2 := by simp
_ = leftKeys ++ keysRest := by rw [hk]
_ = leftKeys ++ (medianKey :: rightKeys) := by rw [hkr]
rw [h_cKeys_eq]; simp
have h_median_mem : medianKey ∈ keysOf (BTree.node cKeys cChildren) := by
unfold keysOf; simp [h_median_in_cKeys]
-- Extract ChildBounded bounds (unfold once, using REPL-proven pattern)
unfold ChildBounded at h_cb
rcases h_cb with ⟨h_cb_rel, h_cb_bounds, _⟩
have h_ilen : children.length = keys.length + 1 := by
rcases h_cb_rel with (h_empty | h_eq)
· exfalso
have hlen0 : children.length = 0 := by simpa using h_empty
rw [hlen0] at h_lt; exact Nat.not_lt_zero i h_lt
· exact h_eq
rcases h_cb_bounds i h_lt with ⟨h_lo_raw, h_hi_raw⟩
have hi_le : i ≤ keys.length := by rw [h_ilen] at h_lt; omega
have hchild_eq_get : children[i] = BTree.node cKeys cChildren := by simpa using hchild_eq
-- lower bound (when i>0): keys[i-1] ≤ medianKey
have h_lower (hi_pos : 0 < i) (hi_sub : i-1 < keys.length) :
keys.get ⟨i-1, hi_sub⟩ ≤ medianKey := by
rcases h_lo_raw with (hi0 | h_lo_match)
· exact (Nat.ne_of_gt hi_pos hi0).elim
· simp [hi_sub] at h_lo_match
rw [hchild_eq_get] at h_lo_match
exact h_lo_match medianKey h_median_mem
-- Two cases: i < keys.length or i = keys.length
by_cases hi_len : i < keys.length
· -- i < keys.length: the upper bound keys[i] exists
have h_upper_val : medianKey ≤ keys.get ⟨i, hi_len⟩ := by
simp [hi_len] at h_hi_raw
rw [hchild_eq_get] at h_hi_raw
exact h_hi_raw medianKey h_median_mem
-- Build take i keys ++ [medianKey] pairwise
have h_take_le : ∀ a ∈ take i keys, a ≤ medianKey := by
intro a ha
rcases List.mem_iff_get.mp ha with ⟨n, h_eq⟩
-- n : Fin (take i keys).length, so n.val < i (since length ≤ i)
have hn_val_lt_i : n.val < i :=
calc n.val < (take i keys).length := n.isLt
_ ≤ i := by simp
have hn_len : n.val < keys.length :=
calc n.val < (take i keys).length := n.isLt
_ ≤ keys.length := by simp
-- (take i keys).get n = keys.get ⟨n.val, hn_len⟩
have h_val : a = keys.get ⟨n.val, hn_len⟩ := by
calc a = (take i keys).get n := by rw [h_eq]
_ = keys.get ⟨n.val, hn_len⟩ := by simp
rw [h_val]
-- keys[j] ≤ keys[i-1] (pairwise, j < i) ≤ medianKey (h_lower)
by_cases hi0 : i = 0
· subst hi0; omega
· have hi_pos : 0 < i := Nat.pos_of_ne_zero hi0
have hi_sub : i-1 < keys.length := by omega
have h_pw : keys.get ⟨n.val, hn_len⟩ ≤ keys.get ⟨i-1, hi_sub⟩ :=
pairwise_get_mono h_keys_pairwise (by omega) hn_len hi_sub
exact Nat.le_trans h_pw (h_lower hi_pos hi_sub)
-- Build medianKey ≤ ∀ b ∈ drop i keys
have h_drop_le : ∀ b ∈ drop i keys, medianKey ≤ b := by
intro b hb
rcases List.mem_iff_get.mp hb with ⟨n, h_eq⟩
-- n : Fin (drop i keys).length
-- (drop i keys).get n = keys.get ⟨i + n.val, ...⟩
have hn_total_len : i + n.val < keys.length := by
have : (drop i keys).length = keys.length - i := by simp
have : n.val < keys.length - i := by
rw [← this]; exact n.isLt
omega
have h_val : b = keys.get ⟨i + n.val, hn_total_len⟩ := by
calc b = (drop i keys).get n := by rw [h_eq]
_ = keys.get ⟨i + n.val, hn_total_len⟩ := by simp
rw [h_val]
-- medianKey ≤ keys[i] (h_upper_val) ≤ keys[i + n.val] (pairwise, i ≤ i+n.val)
have h_pw : keys.get ⟨i, hi_len⟩ ≤ keys.get ⟨i + n.val, hn_total_len⟩ :=
pairwise_get_mono h_keys_pairwise (by omega) hi_len hn_total_len
exact Nat.le_trans h_upper_val h_pw
-- Assemble with pairwise_append
have h_singleton : List.Pairwise (· ≤ ·) [medianKey] := by simp
have h_prefix : List.Pairwise (· ≤ ·) (take i keys ++ [medianKey]) :=
(List.pairwise_append (l₁ := take i keys) (l₂ := [medianKey])).mpr
⟨h_take_pw, h_singleton, λ a ha b hb => by
simp at hb; subst hb; exact h_take_le a ha⟩
-- Need to rewrite the goal to match pairwise_append's l₁ ++ l₂ pattern
have h_assoc : take i keys ++ medianKey :: drop i keys = (take i keys ++ [medianKey]) ++ drop i keys := by simp
rw [h_assoc]
exact ((List.pairwise_append (l₁ := take i keys ++ [medianKey]) (l₂ := drop i keys)).mpr
⟨h_prefix, h_drop_pw, λ a ha b hb => by
rw [List.mem_append] at ha; rcases ha with (ha | ha)
· exact h_cross a ha b hb
· simp at ha; subst ha; exact h_drop_le b hb⟩)
· -- i = keys.length: no upper bound key, drop i keys = []
have hi_eq : i = keys.length := by omega
have h_drop_empty : drop i keys = [] := by rw [hi_eq]; simp
rw [h_drop_empty]
-- Goal: List.Pairwise (· ≤ ·) (take i keys ++ medianKey :: [])
-- medianKey :: [] = [medianKey]
have h_cons_nil : medianKey :: [] = [medianKey] := by simp
rw [h_cons_nil]
-- Goal: List.Pairwise (· ≤ ·) (take i keys ++ [medianKey])
-- Same as the h_prefix proof above, but we use h_take_pw from the outer scope
have h_take_le : ∀ a ∈ take i keys, a ≤ medianKey := by
intro a ha
rcases List.mem_iff_get.mp ha with ⟨n, h_eq⟩
have hn_len : n.val < keys.length :=
Nat.lt_of_lt_of_le n.isLt (by simp)
have h_val : a = keys.get ⟨n.val, hn_len⟩ := by
calc a = (take i keys).get n := by rw [h_eq]
_ = keys.get ⟨n.val, hn_len⟩ := by simp
rw [h_val]
by_cases hi0 : i = 0
· subst hi0; omega
· have hi_pos : 0 < i := Nat.pos_of_ne_zero hi0
have hi_sub : i-1 < keys.length := by omega
have h_pw : keys.get ⟨n.val, hn_len⟩ ≤ keys.get ⟨i-1, hi_sub⟩ :=
pairwise_get_mono h_keys_pairwise (by omega) hn_len hi_sub
exact Nat.le_trans h_pw (h_lower hi_pos hi_sub)
have h_singleton : List.Pairwise (· ≤ ·) [medianKey] := by simp
exact (List.pairwise_append (l₁ := take i keys) (l₂ := [medianKey])).mpr
⟨h_take_pw, h_singleton, λ a ha b hb => by simp at hb; subst hb; exact h_take_le a ha⟩
unfold Sorted
refine ⟨h_keys_ok, h_newChildren_sorted⟩ChildBounded preservation infrastructure
The proof of splitChild_preserves_childBounded relies on:
-
keysOf_node_subset: the keys of a node built from sublists is a subset. -
childBounded_node_nil: a node with no children is trivially bounded. -
keysOf_take_le_pivot/keysOf_drop_ge_pivot: the median key sandwiches the two new children (this is the ordering content that needsSorted). -
childBounded_take_of_full/childBounded_drop_of_full:ChildBoundedsurvives truncating a full node's keys/children to a prefix/suffix.
If ks ⊆ ks' and cs ⊆ cs', then the flattened keys of node ks cs are a
subset of those of node ks' cs'. This is the user-suggested keysOf_subset
lemma, phrased for arbitrary sublists (used for both the left and right split
children).
lemma keysOf_node_subset {ks ks' : List Nat} {cs cs' : List BTree}
(hk : ks ⊆ ks') (hc : cs ⊆ cs') :
keysOf (node ks cs) ⊆ keysOf (node ks' cs') := by
intro x hx
simp only [keysOf, List.mem_append, List.mem_flatMap] at hx ⊢
rcases hx with hxk | ⟨c, hcm, hxc⟩
· exact Or.inl (hk hxk)
· exact Or.inr ⟨c, hc hcm, hxc⟩
A node with no children is trivially ChildBounded.
lemma childBounded_node_nil (ks : List Nat) : ChildBounded (node ks []) := by
unfold ChildBounded
refine ⟨Or.inl (by simp), ?_, ?_⟩
· intro j hj; simp at hj
· intro c hc; simp at hc
Every key beneath the left split node node (ks.take m) (cs.take (m+1)) is
≤ ks[m] (the median key). Uses sortedness of ks and the child's own
ChildBounded upper bounds.
lemma keysOf_take_le_pivot {ks : List Nat} {cs : List BTree} {m : Nat}
(h_pw : List.Pairwise (· ≤ ·) ks)
(h_cb : ChildBounded (node ks cs))
(hm : m < ks.length) :
∀ k ∈ keysOf (node (ks.take m) (cs.take (m + 1))), k ≤ ks[m] := by
intro k hk
simp only [keysOf, List.mem_append, List.mem_flatMap] at hk
rcases hk with hk | ⟨c, hc, hkc⟩
· -- key from the truncated key list: monotone since `ks` is sorted
rcases List.mem_iff_get.mp hk with ⟨n, h_eq⟩
have hn_m : n.val < m := Nat.lt_of_lt_of_le n.isLt (List.length_take_le m ks)
have hn_ks : n.val < ks.length := by omega
have h_val : k = ks.get ⟨n.val, hn_ks⟩ := by
calc k = (ks.take m).get n := by rw [h_eq]
_ = ks.get ⟨n.val, hn_ks⟩ := by simp
rw [h_val]
exact pairwise_get_mono h_pw (by omega) hn_ks hm
· -- key from a child subtree: bounded by `ks[n] ≤ ks[m]`
rcases List.mem_iff_get.mp hc with ⟨n, h_eq⟩
have hn_m1 : n.val < m + 1 := Nat.lt_of_lt_of_le n.isLt (List.length_take_le (m + 1) cs)
have hn_cs : n.val < cs.length := Nat.lt_of_lt_of_le n.isLt (List.length_take_le' (m + 1) cs)
have hn_ks : n.val < ks.length := by omega
have hc_eq : c = cs.get ⟨n.val, hn_cs⟩ := by
calc c = (cs.take (m + 1)).get n := by rw [h_eq]
_ = cs.get ⟨n.val, hn_cs⟩ := by simp
unfold ChildBounded at h_cb
rcases h_cb with ⟨_, h_bounds, _⟩
have hub := (h_bounds n.val hn_cs).2
simp only [List.getElem?_eq_getElem hn_ks] at hub
rw [← hc_eq] at hub
have h1 : k ≤ ks[n.val] := hub k hkc
have h2 := pairwise_get_mono h_pw (show n.val ≤ m by omega) hn_ks hm
simp only [List.get_eq_getElem] at h2
exact le_trans h1 h2
Every key beneath the right split node node (ks.drop (m+1)) (cs.drop (m+1))
is ≥ ks[m] (the median key). Symmetric to keysOf_take_le_pivot.
lemma keysOf_drop_ge_pivot {ks : List Nat} {cs : List BTree} {m : Nat}
(h_pw : List.Pairwise (· ≤ ·) ks)
(h_cb : ChildBounded (node ks cs))
(hm : m < ks.length) :
∀ k ∈ keysOf (node (ks.drop (m + 1)) (cs.drop (m + 1))), ks[m] ≤ k := by
intro k hk
simp only [keysOf, List.mem_append, List.mem_flatMap] at hk
rcases hk with hk | ⟨c, hc, hkc⟩
· -- key from the truncated key list
rcases List.mem_iff_get.mp hk with ⟨n, h_eq⟩
have hn_len : (m + 1) + n.val < ks.length := by
have h : n.val < ks.length - (m + 1) := by rw [← List.length_drop]; exact n.isLt
omega
have h_val : k = ks.get ⟨(m + 1) + n.val, hn_len⟩ := by
calc k = (ks.drop (m + 1)).get n := by rw [h_eq]
_ = ks.get ⟨(m + 1) + n.val, hn_len⟩ := by simp
rw [h_val]
exact pairwise_get_mono h_pw (by omega) hm hn_len
· -- key from a child subtree: bounded by `ks[m] ≤ ks[(m+1)+n-1]`
rcases List.mem_iff_get.mp hc with ⟨n, h_eq⟩
have hn_cs : (m + 1) + n.val < cs.length := by
have h : n.val < cs.length - (m + 1) := by rw [← List.length_drop]; exact n.isLt
omega
unfold ChildBounded at h_cb
rcases h_cb with ⟨h_rel, h_bounds, _⟩
have h_len : cs.length = ks.length + 1 := by
rcases h_rel with h_empty | h_len
· have hnil : cs = [] := List.isEmpty_iff.mp h_empty
have hlen0 : cs.length = 0 := by simp [hnil]
omega
· exact h_len
have hidx : (m + 1) + n.val - 1 < ks.length := by omega
have hc_eq : c = cs.get ⟨(m + 1) + n.val, hn_cs⟩ := by
calc c = (cs.drop (m + 1)).get n := by rw [h_eq]
_ = cs.get ⟨(m + 1) + n.val, hn_cs⟩ := by simp
have hlb := (h_bounds ((m + 1) + n.val) hn_cs).1
rcases hlb with h0 | hlbmatch
· omega
· simp only [List.getElem?_eq_getElem hidx] at hlbmatch
rw [← hc_eq] at hlbmatch
have h1 : ks[(m + 1) + n.val - 1] ≤ k := hlbmatch k hkc
have h2 := pairwise_get_mono h_pw (show m ≤ (m + 1) + n.val - 1 by omega) hm hidx
simp only [List.get_eq_getElem] at h2
exact le_trans h2 h1
ChildBounded survives truncating a node's keys to take m and children to
take (m+1) (the left result of a split).
lemma childBounded_take_of_full {ks : List Nat} {cs : List BTree} {m : Nat}
(h_cb : ChildBounded (node ks cs)) (hm : m < ks.length) :
ChildBounded (node (ks.take m) (cs.take (m + 1))) := by
have h_cb' := h_cb
unfold ChildBounded at h_cb'
rcases h_cb' with ⟨h_rel, h_bounds, h_sub⟩
rcases h_rel with h_empty | h_len
· have hcs : cs = [] := by cases cs with | nil => rfl | cons x xs => simp at h_empty
subst hcs
simpa using childBounded_node_nil (ks.take m)
· unfold ChildBounded
refine ⟨?_, ?_, ?_⟩
· right; rw [List.length_take, List.length_take]; omega
· intro j hj
have hj_cs : j < cs.length := by have := hj; rw [List.length_take] at this; omega
have hchild : (cs.take (m + 1)).get ⟨j, hj⟩ = cs.get ⟨j, hj_cs⟩ := by simp
refine ⟨?_, ?_⟩
· rcases Nat.eq_zero_or_pos j with hj0 | hjpos
· exact Or.inl hj0
· right
have hj1_m : j - 1 < m := by have := hj; rw [List.length_take] at this; omega
have hj1_ks : j - 1 < ks.length := by omega
rw [List.getElem?_take_of_lt hj1_m, List.getElem?_eq_getElem hj1_ks]
have hlb := (h_bounds j hj_cs).1
rcases hlb with h0 | hlbmatch
· omega
· simp only [List.getElem?_eq_getElem hj1_ks] at hlbmatch
intro k hk; rw [hchild] at hk; exact hlbmatch k hk
· by_cases hj_m : j < m
· have hj_ks : j < ks.length := by omega
rw [List.getElem?_take_of_lt hj_m, List.getElem?_eq_getElem hj_ks]
have hub := (h_bounds j hj_cs).2
simp only [List.getElem?_eq_getElem hj_ks] at hub
intro k hk; rw [hchild] at hk; exact hub k hk
· have hnone : (ks.take m)[j]? = none := by
apply List.getElem?_eq_none; rw [List.length_take]; omega
rw [hnone]; exact trivial
· intro c hc
exact h_sub c ((List.take_subset (m + 1) cs) hc)
ChildBounded survives dropping d keys and d children (the right result
of a split, with d = t).
lemma childBounded_drop_of_full {ks : List Nat} {cs : List BTree} {d : Nat}
(h_cb : ChildBounded (node ks cs)) (hd : 0 < d) (hd_cs : d < cs.length) :
ChildBounded (node (ks.drop d) (cs.drop d)) := by
have h_cb' := h_cb
unfold ChildBounded at h_cb'
rcases h_cb' with ⟨h_rel, h_bounds, h_sub⟩
have h_len : cs.length = ks.length + 1 := by
rcases h_rel with h_empty | h_len
· have hnil : cs = [] := by cases cs with | nil => rfl | cons x xs => simp at h_empty
rw [hnil] at hd_cs; simp at hd_cs
· exact h_len
unfold ChildBounded
refine ⟨?_, ?_, ?_⟩
· right; rw [List.length_drop, List.length_drop]; omega
· intro j hj
have hj_len : j < cs.length - d := by have := hj; rw [List.length_drop] at this; exact this
have hdj_cs : d + j < cs.length := by omega
have hchild : (cs.drop d).get ⟨j, hj⟩ = cs.get ⟨d + j, hdj_cs⟩ := by simp
refine ⟨?_, ?_⟩
· rcases Nat.eq_zero_or_pos j with hj0 | hjpos
· exact Or.inl hj0
· right
have hidx : d + j - 1 < ks.length := by omega
have heq_idx : d + (j - 1) = d + j - 1 := by omega
rw [List.getElem?_drop, heq_idx, List.getElem?_eq_getElem hidx]
have hlb := (h_bounds (d + j) hdj_cs).1
rcases hlb with h0 | hlbmatch
· omega
· simp only [List.getElem?_eq_getElem hidx] at hlbmatch
intro k hk; rw [hchild] at hk; exact hlbmatch k hk
· by_cases hdj : d + j < ks.length
· rw [List.getElem?_drop, List.getElem?_eq_getElem hdj]
have hub := (h_bounds (d + j) hdj_cs).2
simp only [List.getElem?_eq_getElem hdj] at hub
intro k hk; rw [hchild] at hk; exact hub k hk
· have hnone : (ks.drop d)[j]? = none := by
rw [List.getElem?_drop]; apply List.getElem?_eq_none; omega
rw [hnone]; exact trivial
· intro c hc
exact h_sub c ((List.drop_subset d cs) hc)
B-TREE-SPLIT-CHILD preserves ChildBounded. Splitting a full child of a
non-full node keeps the key-range invariant: the promoted median key becomes a
new separator that sandwiches the two halves, and every other separator/child
relation is inherited from the original tree.
theorem splitChild_preserves_childBounded (t : Nat) (ht : 2 ≤ t)
(keys : List Nat) (children : List BTree)
(cKeys : List Nat) (cChildren : List BTree) (i : Nat)
(h_lt : i < children.length)
(hchild_eq : children.get ⟨i, h_lt⟩ = node cKeys cChildren)
(hchild_full : cKeys.length = 2 * t - 1)
(h_cb : ChildBounded (node keys children))
(h_sorted : Sorted (node keys children)) :
ChildBounded (splitChild t (node keys children) i) := by
-- Extract the parent's ChildBounded components.
have h_cb' := h_cb
unfold ChildBounded at h_cb'
obtain ⟨h_cb_rel, h_cb_bounds, h_cb_sub⟩ := h_cb'
have h_ch_len : children.length = keys.length + 1 := by
rcases h_cb_rel with h_empty | h_eq
· have hnil : children = [] := List.isEmpty_iff.mp h_empty
have : children.length = 0 := by simp [hnil]
omega
· exact h_eq
have h_i_le_keys : i ≤ keys.length := by omega
-- Unfold `splitChild` (same pattern as `splitChild_preserves_sorted`).
have h_keys_snd_nonempty : (cKeys.splitAt (t - 1)).2 ≠ [] := by
have hlen : (cKeys.splitAt (t - 1)).2.length = t := by simp [hchild_full]; omega
intro h; rw [h] at hlen; simp at hlen; omega
dsimp [splitChild]; rw [dif_pos h_lt]
have h_get : children[i] = node cKeys cChildren := by simpa using hchild_eq
rw [h_get]; dsimp; rw [if_pos hchild_full]
cases hk : cKeys.splitAt (t - 1) with
| mk leftKeys keysRest =>
have h_keysRest_nonempty : keysRest ≠ [] := by
have : (cKeys.splitAt (t - 1)).2 = keysRest := by rw [hk]
rw [← this]; exact h_keys_snd_nonempty
cases hkr : keysRest with
| nil => exact (h_keysRest_nonempty hkr).elim
| cons medianKey rightKeys =>
cases hc : cChildren.splitAt t with
| mk leftCh rightCh =>
show ChildBounded (BTree.node (take i keys ++ medianKey :: drop i keys)
(take i children ++ [BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i + 1) children))
-- Relate the local split names to `take`/`drop` of the child's keys/children.
have h_lk : leftKeys = cKeys.take (t - 1) := by
calc leftKeys = (cKeys.splitAt (t - 1)).1 := by rw [hk]
_ = cKeys.take (t - 1) := by simp
have h_keysRest_eq : keysRest = cKeys.drop (t - 1) := by
calc keysRest = (cKeys.splitAt (t - 1)).2 := by rw [hk]
_ = cKeys.drop (t - 1) := by simp
have h_rk : rightKeys = cKeys.drop t := by
calc rightKeys = keysRest.drop 1 := by rw [hkr]; simp
_ = (cKeys.drop (t - 1)).drop 1 := by rw [h_keysRest_eq]
_ = cKeys.drop ((t - 1) + 1) := by rw [← List.drop_drop]
_ = cKeys.drop t := by rw [show (t - 1) + 1 = t from by omega]
have h_left_eq : leftCh = cChildren.take t := by
calc leftCh = (cChildren.splitAt t).1 := by rw [hc]
_ = cChildren.take t := by simp
have h_right_eq : rightCh = cChildren.drop t := by
calc rightCh = (cChildren.splitAt t).2 := by rw [hc]
_ = cChildren.drop t := by simp
have h_t1_lt : t - 1 < cKeys.length := by omega
have h_median : cKeys[t - 1]? = some medianKey := by
have hh : (cKeys.drop (t - 1))[0]? = some medianKey := by
rw [← h_keysRest_eq, hkr]; rfl
rw [List.getElem?_drop] at hh; simpa using hh
have h_median_eq : medianKey = cKeys[t - 1] := by
rw [List.getElem?_eq_getElem h_t1_lt] at h_median
injection h_median with h_median; exact h_median.symm
-- Child invariants.
have h_child_cb : ChildBounded (node cKeys cChildren) := by
rw [← hchild_eq]; apply h_cb_sub; apply List.get_mem
have h_cKeys_pw : List.Pairwise (· ≤ ·) cKeys := by
have h_cs : Sorted (node cKeys cChildren) := by
rw [← hchild_eq]
unfold Sorted at h_sorted; rcases h_sorted with ⟨_, h_sc⟩
apply h_sc; apply List.get_mem
unfold Sorted at h_cs; exact h_cs.1
have h_cChildren_len := child_children_len_of_full_cb ht h_child_cb hchild_full
-- The median key sandwiches the two new children.
have h_left_le : ∀ k ∈ keysOf (node leftKeys leftCh), k ≤ medianKey := by
intro k hk
rw [h_lk, h_left_eq] at hk
rw [h_median_eq]
have hp := keysOf_take_le_pivot h_cKeys_pw h_child_cb h_t1_lt
rw [show (t - 1) + 1 = t from by omega] at hp
exact hp k hk
have h_right_ge : ∀ k ∈ keysOf (node rightKeys rightCh), medianKey ≤ k := by
intro k hk
rw [h_rk, h_right_eq] at hk
rw [h_median_eq]
have hp := keysOf_drop_ge_pivot h_cKeys_pw h_child_cb h_t1_lt
rw [show (t - 1) + 1 = t from by omega] at hp
exact hp k hk
-- The two new children are themselves ChildBounded.
have h_cb_left : ChildBounded (node leftKeys leftCh) := by
have hh := childBounded_take_of_full h_child_cb h_t1_lt
rw [show (t - 1) + 1 = t from by omega] at hh
rw [h_lk, h_left_eq]; exact hh
have h_cb_right : ChildBounded (node rightKeys rightCh) := by
rcases h_cChildren_len with h0 | h2t
· have hnil : cChildren = [] := by
cases cChildren with | nil => rfl | cons x xs => simp at h0
have hrc : rightCh = [] := by rw [h_right_eq, hnil]; simp
rw [hrc]; exact childBounded_node_nil rightKeys
· have hd_cs : t < cChildren.length := by rw [h2t]; omega
have hh := childBounded_drop_of_full h_child_cb (by omega) hd_cs
rw [h_rk, h_right_eq]; exact hh
-- `newKeys[·]?` computed by position relative to the inserted median.
have h_P_len : (take i keys).length = i := by rw [List.length_take]; omega
have hNK_lt : ∀ j', j' < i →
(take i keys ++ medianKey :: drop i keys)[j']? = keys[j']? := by
intro j' hj'
rw [List.getElem?_append_left (by rw [h_P_len]; exact hj'), List.getElem?_take_of_lt hj']
have hNK_eq : (take i keys ++ medianKey :: drop i keys)[i]? = some medianKey := by
rw [List.getElem?_append_right (le_of_eq h_P_len), h_P_len]; simp
have hNK_gt : ∀ j', i < j' →
(take i keys ++ medianKey :: drop i keys)[j']? = keys[j' - 1]? := by
intro j' hj'
rw [List.getElem?_append_right (by rw [h_P_len]; omega), h_P_len,
show j' - i = (j' - i - 1) + 1 from by omega, List.getElem?_cons_succ,
List.getElem?_drop, show i + (j' - i - 1) = j' - 1 from by omega]
unfold ChildBounded
refine ⟨?_, ?_, ?_⟩
· -- Count relation.
right
simp only [List.length_append, List.length_cons, List.length_nil,
List.length_take, List.length_drop]
omega
· -- Parent key-range bounds.
have h_A_len : (take i children).length = i := by rw [List.length_take]; omega
have h_AB_len : (take i children ++
[node leftKeys leftCh, node rightKeys rightCh]).length = i + 2 := by
simp [List.length_append, h_A_len]
have h_nc_len : (take i children ++
[node leftKeys leftCh, node rightKeys rightCh] ++ drop (i + 1) children).length
= children.length + 1 := by
simp only [List.length_append, List.length_cons, List.length_nil,
List.length_take, List.length_drop]
omega
have hsub_left : keysOf (node leftKeys leftCh) ⊆ keysOf (node cKeys cChildren) :=
keysOf_node_subset (by rw [h_lk]; exact List.take_subset _ _)
(by rw [h_left_eq]; exact List.take_subset _ _)
have hsub_right : keysOf (node rightKeys rightCh) ⊆ keysOf (node cKeys cChildren) :=
keysOf_node_subset (by rw [h_rk]; exact List.drop_subset _ _)
(by rw [h_right_eq]; exact List.drop_subset _ _)
intro j hj
have hj' : j < children.length + 1 := h_nc_len ▸ hj
rcases Nat.lt_trichotomy j i with hlt | heq | hgt
· -- Region 1: `j < i` — unchanged left children.
have hj_ch : j < children.length := by omega
have hlt_AB : j < (take i children ++
[node leftKeys leftCh, node rightKeys rightCh]).length := by rw [h_AB_len]; omega
have hlt_A : j < (take i children).length := by rw [h_A_len]; omega
have hchild : (take i children ++ [node leftKeys leftCh, node rightKeys rightCh] ++
drop (i + 1) children).get ⟨j, hj⟩ = children.get ⟨j, hj_ch⟩ := by
simp only [List.get_eq_getElem]
rw [List.getElem_append_left hlt_AB, List.getElem_append_left hlt_A]; simp
refine ⟨?_, ?_⟩
· rcases Nat.eq_zero_or_pos j with hj0 | hjpos
· exact Or.inl hj0
· right
rw [hNK_lt (j - 1) (by omega), hchild]
rcases (h_cb_bounds j hj_ch).1 with h0 | hbmatch
· omega
· exact hbmatch
· rw [hNK_lt j hlt, hchild]
exact (h_cb_bounds j hj_ch).2
· -- Region 2: `j = i` — the new left child.
have hlt_AB : j < (take i children ++
[node leftKeys leftCh, node rightKeys rightCh]).length := by rw [h_AB_len]; omega
have hge_A : (take i children).length ≤ j := by rw [h_A_len]; omega
have hchild : (take i children ++ [node leftKeys leftCh, node rightKeys rightCh] ++
drop (i + 1) children).get ⟨j, hj⟩ = node leftKeys leftCh := by
simp only [List.get_eq_getElem]
rw [List.getElem_append_left hlt_AB, List.getElem_append_right hge_A]
simp [h_A_len, show j - i = 0 from by omega]
refine ⟨?_, ?_⟩
· rcases Nat.eq_zero_or_pos j with hj0 | hjpos
· exact Or.inl hj0
· right
rw [hNK_lt (j - 1) (by omega), hchild, show j - 1 = i - 1 from by omega]
have hb := (h_cb_bounds i h_lt).1
rw [hchild_eq] at hb
rcases hb with h0 | hbmatch
· omega
· revert hbmatch
cases keys[i - 1]? with
| none => intro _; trivial
| some lo => intro hbmatch; exact fun k hk => hbmatch k (hsub_left hk)
· have hjk : (take i keys ++ medianKey :: drop i keys)[j]? = some medianKey := by
rw [show j = i from heq]; exact hNK_eq
rw [hjk, hchild]; exact h_left_le
· rcases Nat.lt_or_ge j (i + 2) with hj2 | hj2
· -- Region 3: `j = i + 1` — the new right child.
have hlt_AB : j < (take i children ++
[node leftKeys leftCh, node rightKeys rightCh]).length := by rw [h_AB_len]; omega
have hge_A : (take i children).length ≤ j := by rw [h_A_len]; omega
have hchild : (take i children ++ [node leftKeys leftCh, node rightKeys rightCh] ++
drop (i + 1) children).get ⟨j, hj⟩ = node rightKeys rightCh := by
simp only [List.get_eq_getElem]
rw [List.getElem_append_left hlt_AB, List.getElem_append_right hge_A]
simp [h_A_len, show j - i = 1 from by omega]
refine ⟨?_, ?_⟩
· right
rw [show j - 1 = i from by omega, hNK_eq, hchild]
exact h_right_ge
· rw [hNK_gt j (by omega), show j - 1 = i from by omega, hchild]
have hb := (h_cb_bounds i h_lt).2
rw [hchild_eq] at hb
revert hb
cases keys[i]? with
| none => intro _; trivial
| some hi => intro hb; exact fun k hk => hb k (hsub_right hk)
· -- Region 4: `j ≥ i + 2` — unchanged right children (shifted by one).
have hj1_ch : j - 1 < children.length := by omega
have hge_AB : (take i children ++
[node leftKeys leftCh, node rightKeys rightCh]).length ≤ j := by
rw [h_AB_len]; omega
have hchild : (take i children ++ [node leftKeys leftCh, node rightKeys rightCh] ++
drop (i + 1) children).get ⟨j, hj⟩ = children.get ⟨j - 1, hj1_ch⟩ := by
simp only [List.get_eq_getElem]
rw [List.getElem_append_right hge_AB]
simp only [h_AB_len, List.getElem_drop,
show (i + 1) + (j - (i + 2)) = j - 1 from by omega]
refine ⟨?_, ?_⟩
· right
rw [hNK_gt (j - 1) (by omega), hchild]
rcases (h_cb_bounds (j - 1) hj1_ch).1 with h0 | hbmatch
· omega
· exact hbmatch
· rw [hNK_gt j (by omega), hchild]
exact (h_cb_bounds (j - 1) hj1_ch).2
· -- Recursive ChildBounded of every new child.
intro child hchild
rcases List.mem_append.mp hchild with h_take_new | h_drop
· rcases List.mem_append.mp h_take_new with h_take | h_new
· exact h_cb_sub child ((List.take_subset i children) h_take)
· simp at h_new; rcases h_new with rfl | rfl
· exact h_cb_left
· exact h_cb_right
· exact h_cb_sub child ((List.drop_subset (i + 1) children) h_drop)
B-TREE-SPLIT-CHILD preserves WellFormed. Splitting a full child i of a
non-full node keeps all four structural invariants simultaneously. This is the
capstone that combines splitChild_preserves_sorted,
splitChild_preserves_childBounded, splitChild_preserves_occupancy, and
splitChild_preserves_sameDepth. The side condition
cChildren = [] ∨ t < cChildren.length needed by the SameDepth lemma is
derived from the child's own ChildBounded invariant.
theorem splitChild_preserves_wellFormed (t : Nat) (ht : 2 ≤ t)
(keys : List Nat) (children : List BTree)
(cKeys : List Nat) (cChildren : List BTree) (i : Nat)
(h_lt : i < children.length)
(hchild_eq : children.get ⟨i, h_lt⟩ = node cKeys cChildren)
(hchild_full : cKeys.length = 2 * t - 1)
(hparent_nonfull : keys.length < 2 * t - 1)
(h_wf : WellFormed t (node keys children)) :
WellFormed t (splitChild t (node keys children) i) := by
obtain ⟨h_sorted, h_cb, h_occ, h_sd⟩ := h_wf
-- The split child is itself `ChildBounded`, so its child count is `0` or `2t`.
have h_child_cb : ChildBounded (node cKeys cChildren) := by
rw [← hchild_eq]
have hcb := h_cb
unfold ChildBounded at hcb; rcases hcb with ⟨_, _, h_sub⟩
apply h_sub; apply List.get_mem
have hchild_children : cChildren = [] ∨ t < cChildren.length := by
rcases child_children_len_of_full_cb ht h_child_cb hchild_full with h0 | h2t
· left; cases cChildren with | nil => rfl | cons x xs => simp at h0
· right; rw [h2t]; omega
exact ⟨splitChild_preserves_sorted t ht keys children cKeys cChildren i h_lt hchild_eq
hchild_full h_sorted h_cb,
splitChild_preserves_childBounded t ht keys children cKeys cChildren i h_lt hchild_eq
hchild_full h_cb h_sorted,
splitChild_preserves_occupancy t ht keys children cKeys cChildren i h_lt hchild_eq
hchild_full hparent_nonfull h_occ h_cb,
splitChild_preserves_sameDepth t ht keys children cKeys cChildren i h_lt hchild_eq
hchild_full hchild_children h_sd⟩
B-TREE-SPLIT-CHILD preserves the key multiset. Splitting a full child
neither loses nor introduces keys: the flattened key list of the result is a
permutation of the original. No ordering hypotheses are needed — this is a
pure contents fact, complementing the structural-invariant theorems above.
theorem splitChild_keys_perm (t : Nat) (ht : 2 ≤ t)
(keys : List Nat) (children : List BTree)
(cKeys : List Nat) (cChildren : List BTree) (i : Nat)
(h_lt : i < children.length)
(hchild_eq : children.get ⟨i, h_lt⟩ = node cKeys cChildren)
(hchild_full : cKeys.length = 2 * t - 1) :
(keysOf (splitChild t (node keys children) i)).Perm (keysOf (node keys children)) := by
have h_keys_snd_nonempty : (cKeys.splitAt (t - 1)).2 ≠ [] := by
have hlen : (cKeys.splitAt (t - 1)).2.length = t := by simp [hchild_full]; omega
intro h; rw [h] at hlen; simp at hlen; omega
dsimp [splitChild]; rw [dif_pos h_lt]
have h_get : children[i] = node cKeys cChildren := by simpa using hchild_eq
rw [h_get]; dsimp; rw [if_pos hchild_full]
cases hk : cKeys.splitAt (t - 1) with
| mk leftKeys keysRest =>
have h_keysRest_nonempty : keysRest ≠ [] := by
have : (cKeys.splitAt (t - 1)).2 = keysRest := by rw [hk]
rw [← this]; exact h_keys_snd_nonempty
cases hkr : keysRest with
| nil => exact (h_keysRest_nonempty hkr).elim
| cons medianKey rightKeys =>
cases hc : cChildren.splitAt t with
| mk leftCh rightCh =>
show (keysOf (BTree.node (take i keys ++ medianKey :: drop i keys)
(take i children ++ [BTree.node leftKeys leftCh, BTree.node rightKeys rightCh] ++
drop (i + 1) children))).Perm (keysOf (node keys children))
have h_lk : leftKeys = cKeys.take (t - 1) := by
calc leftKeys = (cKeys.splitAt (t - 1)).1 := by rw [hk]
_ = cKeys.take (t - 1) := by simp
have h_keysRest_eq : keysRest = cKeys.drop (t - 1) := by
calc keysRest = (cKeys.splitAt (t - 1)).2 := by rw [hk]
_ = cKeys.drop (t - 1) := by simp
have h_left_eq : leftCh = cChildren.take t := by
calc leftCh = (cChildren.splitAt t).1 := by rw [hc]
_ = cChildren.take t := by simp
have h_right_eq : rightCh = cChildren.drop t := by
calc rightCh = (cChildren.splitAt t).2 := by rw [hc]
_ = cChildren.drop t := by simp
-- The three list decompositions that make the multiset match up.
have h_cKeys_decomp : cKeys = leftKeys ++ medianKey :: rightKeys := by
conv_lhs => rw [← List.take_append_drop (t - 1) cKeys]
rw [← h_lk, ← h_keysRest_eq, hkr]
have h_cChildren_decomp : cChildren = leftCh ++ rightCh := by
conv_lhs => rw [← List.take_append_drop t cChildren]
rw [← h_left_eq, ← h_right_eq]
have h_children_decomp :
children = take i children ++ node cKeys cChildren :: drop (i + 1) children := by
conv_lhs => rw [← List.take_append_drop i children]
rw [List.drop_eq_getElem_cons h_lt, h_get]
-- Reduce the permutation to a multiset equality and linearise both sides.
rw [← Multiset.coe_eq_coe]
conv_rhs => rw [keysOf, h_children_decomp]
conv_lhs => rw [keysOf]
simp only [List.flatMap_append, List.flatMap_cons, List.flatMap_nil, List.append_nil,
keysOf, h_cKeys_decomp, h_cChildren_decomp, ← Multiset.coe_add, ← Multiset.coe_nil,
← Multiset.cons_coe, ← Multiset.singleton_add]
rw [show (↑keys : Multiset Nat) = ↑(take i keys) + ↑(drop i keys) from by
rw [Multiset.coe_add, List.take_append_drop]]
abelend BTreeend Chapter18end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_18.Section_18_1_B_Tree_Model.HeightBound
CLRS Section 18.1 - B-tree key count and height bound
This module counts every key slot represented by a B-tree. Its exact accounting identity rewrites an internal node's augmented key count as the sum of the augmented counts of its children. That recurrence is the arithmetic foundation for the structural minimum-key and logarithmic-height bounds.
namespace CLRSnamespace Chapter18namespace BTreeExact key accounting
The number of key slots represented by a B-tree.
A node's total key count is its local key count plus all child counts.
theorem totalKeys_node (ks : List Nat) (cs : List BTree) :
totalKeys (node ks cs) =
ks.length + (cs.map totalKeys).sum := by
unfold totalKeys
simp [keysOf, List.length_flatMap]For an internal node, adding one to the total key count exactly absorbs every separator key into one augmented count per child.
private lemma totalKeys_add_one_eq_sum_children
{ks : List Nat} {c0 : BTree} {cs : List BTree}
(hcb : ChildBounded (node ks (c0 :: cs))) :
totalKeys (node ks (c0 :: cs)) + 1 =
((c0 :: cs).map (fun child => totalKeys child + 1)).sum := by
have hlen : (c0 :: cs).length = ks.length + 1 := by
unfold ChildBounded at hcb
simpa using hcb.1
rw [totalKeys_node, List.sum_map_add, List.map_const', List.sum_const_nat]
simp only [Nat.mul_one]
omegaA pointwise lower bound lifts to the sum over all list positions.
private lemma length_mul_le_sum_map
{α : Type} (xs : List α) (q : Nat) (f : α → Nat)
(hpoint : ∀ x ∈ xs, q ≤ f x) :
xs.length * q ≤ (xs.map f).sum := by
have hsum : (xs.map (fun _ => q)).sum ≤ (xs.map f).sum :=
List.sum_le_sum hpoint
rw [List.map_const', List.sum_const_nat] at hsum
exact hsumInternal-child projections
Every child position of a child-bounded internal node is child-bounded.
private lemma childBounded_of_mem
{ks : List Nat} {c0 child : BTree} {cs : List BTree}
(hcb : ChildBounded (node ks (c0 :: cs)))
(hc : child ∈ c0 :: cs) :
ChildBounded child := by
unfold ChildBounded at hcb
exact hcb.2.2 child hcEvery child position of an occupied node satisfies non-root occupancy.
private lemma occupancy_false_of_mem
{t : Nat} {isRoot : Bool} {ks : List Nat} {c0 child : BTree}
{cs : List BTree}
(hocc : Occupancy t isRoot (node ks (c0 :: cs)))
(hc : child ∈ c0 :: cs) :
Occupancy t false child := by
unfold Occupancy at hocc
exact hocc.2.2.2 child hcEvery child position of a same-depth internal node is itself same-depth.
private lemma sameDepth_of_mem
{ks : List Nat} {c0 child : BTree} {cs : List BTree}
(hsd : SameDepth (node ks (c0 :: cs)))
(hc : child ∈ c0 :: cs) :
SameDepth child := by
rcases List.mem_cons.mp hc with rfl | hcTail
· exact sameDepth_head_sd hsd
· exact sameDepth_tail_sd hsd child hcTailEvery child of a same-depth internal node has the head child's height.
private lemma heightOf_eq_head_of_mem
{ks : List Nat} {c0 child : BTree} {cs : List BTree}
(hsd : SameDepth (node ks (c0 :: cs)))
(hc : child ∈ c0 :: cs) :
heightOf child = heightOf c0 :=
sameDepth_children_eq_height hsd child hc c0 (by simp)Non-root minimum-key bound
Every non-root B-tree subtree contains enough key slots for its height. The augmented form avoids natural-number subtraction and is the induction theorem used by the root-level CLRS bound.
theorem nonRoot_totalKeys_add_one_lower_bound
(t : Nat) (_ht : 2 ≤ t) {tr : BTree}
(hcb : ChildBounded tr)
(hocc : Occupancy t false tr)
(hsd : SameDepth tr) :
t ^ (heightOf tr + 1) ≤ totalKeys tr + 1 := by
induction hsd with
| leaf ks =>
simp [Occupancy] at hocc
simp [heightOf, totalKeys_node]
omega
| internal ks c0 cs hheights hsd0 hsdcs ih0 ihcs =>
have hsdNode : SameDepth (node ks (c0 :: cs)) :=
SameDepth.internal ks c0 cs hheights hsd0 hsdcs
have hcount : t ≤ (c0 :: cs).length := by
have hocc' := hocc
simp [Occupancy] at hocc'
rcases hocc' with ⟨_, _, ⟨hcount, _⟩, _, _⟩
exact hcount
let q := t ^ (heightOf c0 + 1)
have hpoint :
∀ child ∈ c0 :: cs, q ≤ totalKeys child + 1 := by
intro child hc
rcases List.mem_cons.mp hc with rfl | hcTail
· exact ih0
(childBounded_of_mem hcb (by simp))
(occupancy_false_of_mem hocc (by simp))
· have hcMem : child ∈ c0 :: cs := by simp [hcTail]
simpa [q, heightOf_eq_head_of_mem hsdNode hcMem] using
(ihcs child hcTail
(childBounded_of_mem hcb hcMem)
(occupancy_false_of_mem hocc hcMem))
have hsum :
(c0 :: cs).length * q ≤
((c0 :: cs).map (fun child => totalKeys child + 1)).sum :=
length_mul_le_sum_map (c0 :: cs) q
(fun child => totalKeys child + 1) hpoint
have hmul : t * q ≤ (c0 :: cs).length * q :=
Nat.mul_le_mul_right q hcount
rw [totalKeys_add_one_eq_sum_children hcb]
calc
t ^ (heightOf (node ks (c0 :: cs)) + 1) = t * q := by
rw [heightOf_internal_of_sameDepth hsdNode]
have hexponent :
1 + heightOf c0 + 1 = (heightOf c0 + 1) + 1 := by
omega
rw [hexponent, Nat.pow_succ]
exact Nat.mul_comm _ _
_ ≤ (c0 :: cs).length * q := hmul
_ ≤ ((c0 :: cs).map (fun child => totalKeys child + 1)).sum := hsumRoot minimum-key and logarithmic-height bounds
A well-formed root is either the legal empty tree or has the CLRS augmented minimum key count for its height.
theorem wellFormed_empty_or_totalKeys_add_one_lower_bound
(t : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr) :
tr = node [] [] ∨
2 * t ^ heightOf tr ≤ totalKeys tr + 1 := by
rcases hwf with ⟨_hsorted, hcb, hocc, hsd⟩
cases tr with
| node ks children =>
cases children with
| nil =>
by_cases hks : ks = []
· left
simp [hks]
· right
simp [Occupancy, hks] at hocc
simp [heightOf, totalKeys_node]
omega
| cons c0 cs =>
right
have hcount : 2 ≤ (c0 :: cs).length := by
have hocc' := hocc
unfold Occupancy at hocc'
rcases hocc' with ⟨_, _, hchildren, _⟩
rcases hchildren with hchildrenEmpty | hchildrenBounds
· simp at hchildrenEmpty
· exact hchildrenBounds.1
let q := t ^ (heightOf c0 + 1)
have hpoint :
∀ child ∈ c0 :: cs, q ≤ totalKeys child + 1 := by
intro child hc
simpa [q, heightOf_eq_head_of_mem hsd hc] using
(nonRoot_totalKeys_add_one_lower_bound t ht
(childBounded_of_mem hcb hc)
(occupancy_false_of_mem hocc hc)
(sameDepth_of_mem hsd hc))
have hsum :
(c0 :: cs).length * q ≤
((c0 :: cs).map (fun child => totalKeys child + 1)).sum :=
length_mul_le_sum_map (c0 :: cs) q
(fun child => totalKeys child + 1) hpoint
have hmul : 2 * q ≤ (c0 :: cs).length * q :=
Nat.mul_le_mul_right q hcount
rw [totalKeys_add_one_eq_sum_children hcb]
calc
2 * t ^ heightOf (node ks (c0 :: cs)) = 2 * q := by
rw [heightOf_internal_of_sameDepth hsd]
simp [q, Nat.add_comm]
_ ≤ (c0 :: cs).length * q := hmul
_ ≤ ((c0 :: cs).map (fun child => totalKeys child + 1)).sum := hsumA well-formed tree is empty or satisfies the textbook minimum-key expression.
theorem wellFormed_empty_or_minKeys_le_totalKeys
(t : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr) :
tr = node [] [] ∨ minKeys t (heightOf tr) ≤ totalKeys tr := by
rcases wellFormed_empty_or_totalKeys_add_one_lower_bound t ht hwf with
hempty | hbound
· exact Or.inl hempty
· right
unfold minKeys
omegaEvery nonempty well-formed tree satisfies the textbook minimum-key bound.
theorem wellFormed_minKeys_le_totalKeys
(t : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr)
(hne : tr ≠ node [] []) :
minKeys t (heightOf tr) ≤ totalKeys tr := by
rcases wellFormed_empty_or_minKeys_le_totalKeys t ht hwf with
hempty | hbound
· exact (hne hempty).elim
· exact hboundThe height of every well-formed B-tree, including the empty tree, is at most the minimum-degree-base logarithm of its CLRS normalized key count.
theorem wellFormed_height_log_bound
(t : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr) :
heightOf tr ≤ Nat.log t ((totalKeys tr + 1) / 2) := by
rcases wellFormed_empty_or_totalKeys_add_one_lower_bound t ht hwf with
hempty | hbound
· subst tr
simp [heightOf, totalKeys, keysOf]
· have htBase : 1 < t := by omega
apply Nat.le_log_of_pow_le htBase
have htwo : 0 < 2 := by omega
apply (Nat.le_div_iff_mul_le htwo).2
simpa [Nat.mul_comm] using hboundend BTreeend Chapter18end CLRSCLRSLean.FourthEdition.Chapter_18.Section_18_1_B_Tree_Model.RunningTime
CLRS Chapter 18 - B-tree running time
This module bounds recursive-descent charges that follow the branch structure of the B-tree search, insertion, and deletion definitions. Each selected recursive call contributes one unit; terminal cases also contribute one. Top-level insertion adds one budget unit when the root splits.
These counters do not enumerate literal page reads/writes. In particular, split/borrow/merge accesses, separator scans, max/min predecessor/successor traversals, persistent list copying, and root-normalization work are not charged individually. No constant-factor refinement to complete page I/O is proved here.
Main results:
-
searchCost_le_height,insertCost_le_height, anddeleteCost_le_height: the selected recursive path has at mostheight + 1charges. -
insertRootCost_le_height: the insertion descent/root-split budget is at mostheight + 3. -
The historical
*_le_diskAccessBoundtheorems bound these same descent counters on well-formed trees with2 ≤ t. -
diskAccessBound_isBigO_log_t: the common mathematical envelope isO(log_t n). The historical name is retained for compatibility; it does not convert the descent counter into a complete disk-access trace.
Functional insertion/deletion structure, key-bag, and search correctness are independent of this accounting boundary and remain available unchanged.
Notation conventions used in this section:
-
t: B-tree minimum degree (2 ≤ tfor every cost theorem) -
tr: aBTree -
totalKeys tr: the number of represented key slots (thenof CLRS)
namespace CLRSnamespace Chapter18namespace BTreeopen ListRecursive-descent charges
Number of nodes visited by searchExec for key x: one per level
on the separator-selected descent path.
def searchCost (x : Nat) : BTree → Nat
| node ks cs =>
if x ∈ ks then 1
else
match _hc : cs[findChild ks x]? with
| some child => 1 + searchCost x child
| none => 1
termination_by tr => heightOf tr
decreasing_by
exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨findChild ks x, _hc⟩)Recursive-descent charges for the insertion branch structure. A full-child split changes the selected subtree but adds no separate page-read/write events. This is not a literal count of every node inspected or modified.
def insertCost (t x : Nat) : BTree → Nat
| node ks cs =>
if cs.isEmpty then 1
else
let i := findChild ks x
match _hc : cs[i]? with
| none => 1
| some c =>
match _hcc : c with
| node cKeys cChildren =>
if cKeys.length = 2 * t - 1 then
let median := cKeys.getD (t - 1) 0
if x < median then
1 + insertCost t x (node (cKeys.take (t - 1)) (cChildren.take t))
else
1 + insertCost t x (node (cKeys.drop t) (cChildren.drop t))
else
1 + insertCost t x c
termination_by tr => heightOf tr
decreasing_by
all_goals
have hmem : node cKeys cChildren ∈ cs := List.mem_iff_getElem?.mpr ⟨i, _hc⟩
refine lt_of_le_of_lt ?_ (heightOf_mem_lt hmem)
first
| exact le_of_eq (congrArg heightOf _hcc)
| exact heightOf_le_of_children_subset (List.take_subset _ _)
| exact heightOf_le_of_children_subset (List.drop_subset _ _)Recursive-descent charges for deletion. Borrow/merge selects the next subtree; max/min chooses a replacement key. The helper traversals and local page operations are not counted by the added unit at each recursive level.
def deleteCost (t : Nat) (x : Nat) : BTree → Nat
| node ks cs =>
if cs.isEmpty then 1
else
let i := findChild ks x
if hiPos : 0 < i then
let ki := i - 1
match hk : ks[ki]? with
| some k =>
if hkeq : k = x then
match hcl : cs[ki]? with
| some leftChild =>
match hcr : cs[ki + 1]? with
| some rightChild =>
if hla : t ≤ numKeys leftChild then
1 + deleteCost t (maxKey leftChild) leftChild
else if hlb : t ≤ numKeys rightChild then
1 + deleteCost t (minKey rightChild) rightChild
else
1 + deleteCost t x (mergeNodes leftChild k rightChild)
| none => 1
| none => 1
else
match hc : cs[i]? with
| some child =>
if hcg : t ≤ numKeys child then
1 + deleteCost t x child
else
match hls : cs[i - 1]? with
| some leftSib =>
if hlg : t ≤ numKeys leftSib then
match hsep : ks[i - 1]? with
| some sep =>
1 + deleteCost t x (rotateLeft leftSib sep child).2.2
| none => 1 + deleteCost t x child
else
match hrs : cs[i + 1]? with
| some rightSib =>
if hrg : t ≤ numKeys rightSib then
match hsep : ks[i]? with
| some sep =>
1 + deleteCost t x (rotateRight child sep rightSib).1
| none => 1 + deleteCost t x child
else
match hsep : ks[i - 1]? with
| some sep =>
1 + deleteCost t x (mergeNodes leftSib sep child)
| none => 1 + deleteCost t x child
| none =>
match hsep : ks[i - 1]? with
| some sep =>
1 + deleteCost t x (mergeNodes leftSib sep child)
| none => 1 + deleteCost t x child
| none => 1 + deleteCost t x child
| none => 1
| none =>
match hc : cs[i]? with
| some child =>
if hcg : t ≤ numKeys child then
1 + deleteCost t x child
else
match hls : cs[i - 1]? with
| some leftSib =>
if hlg : t ≤ numKeys leftSib then
match hsep : ks[i - 1]? with
| some sep =>
1 + deleteCost t x (rotateLeft leftSib sep child).2.2
| none => 1 + deleteCost t x child
else
match hrs : cs[i + 1]? with
| some rightSib =>
if hrg : t ≤ numKeys rightSib then
match hsep : ks[i]? with
| some sep =>
1 + deleteCost t x (rotateRight child sep rightSib).1
| none => 1 + deleteCost t x child
else
match hsep : ks[i - 1]? with
| some sep =>
1 + deleteCost t x (mergeNodes leftSib sep child)
| none => 1 + deleteCost t x child
| none =>
match hsep : ks[i - 1]? with
| some sep =>
1 + deleteCost t x (mergeNodes leftSib sep child)
| none => 1 + deleteCost t x child
| none => 1 + deleteCost t x child
| none => 1
else
match hc : cs[0]? with
| some child =>
if hcg : t ≤ numKeys child then
1 + deleteCost t x child
else
match hrs : cs[1]? with
| some rightSib =>
if hrg : t ≤ numKeys rightSib then
match hsep : ks[0]? with
| some sep =>
1 + deleteCost t x (rotateRight child sep rightSib).1
| none => 1 + deleteCost t x child
else
match hsep : ks[0]? with
| some sep =>
1 + deleteCost t x (mergeNodes child sep rightSib)
| none => 1 + deleteCost t x child
| none => 1 + deleteCost t x child
| none => 1
termination_by tr => heightOf tr
decreasing_by
· exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki, hcl⟩)
· exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki + 1, hcr⟩)
· rw [heightOf_mergeNodes_eq_max]
have ha : heightOf leftChild < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki, hcl⟩)
have hb : heightOf rightChild < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki + 1, hcr⟩)
omega
all_goals
first
| (rw [heightOf_mergeNodes_eq_max]
first
| (have ha : heightOf leftSib < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hls⟩)
have hb : heightOf child < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩)
omega)
| (have ha : heightOf child < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩)
have hb : heightOf rightSib < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hrs⟩)
omega))
| exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩)
| (have hle := heightOf_rotateLeft_right_le leftSib sep child
have ha : heightOf leftSib < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hls⟩)
have hb : heightOf child < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩)
omega)
| (have hle := heightOf_rotateRight_left_le child sep rightSib
have ha : heightOf child < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩)
have hb : heightOf rightSib < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hrs⟩)
omega)Height bounds
O(h) search. searchExec descends at most one path, so its
number of descent charges is bounded by the tree height plus one.
theorem searchCost_le_height (x : Nat) (tr : BTree) :
searchCost x tr ≤ heightOf tr + 1 := by
induction tr using searchCost.induct x with
| case1 ks cs hxkeys =>
rw [searchCost, if_pos hxkeys]
omega
| case2 ks cs hxkeys child hchild ih =>
rw [searchCost, if_neg hxkeys]
split
· rename_i child' hchild'
rw [hchild] at hchild'
cases hchild'
have hlt : heightOf child < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨findChild ks x, hchild⟩)
omega
· rename_i hnone
rw [hchild] at hnone
contradiction
| case3 ks cs hxkeys hchild =>
rw [searchCost, if_neg hxkeys]
split
· rename_i child hchild'
rw [hchild] at hchild'
contradiction
· omega
O(h) insertion. insertNonFull descends at most one path, so
its number of descent charges is bounded by the tree height plus one.
theorem insertCost_le_height (t x : Nat) (tr : BTree) :
insertCost t x tr ≤ heightOf tr + 1 := by
induction tr using insertCost.induct (t := t) (x := x) with
| case1 ks cs hempty =>
rw [insertCost, if_pos hempty]
omega
| case2 ks cs hne i hnone =>
have hval : insertCost t x (node ks cs) = 1 := by
rw [insertCost, if_neg hne]
dsimp only
split
· omega
· rename_i c hc
rw [hnone] at hc
simp at hc
rw [hval]
omega
| case3 ks cs hne i cKeys cChildren hsome hfull median hlt hsome2 ih =>
have hsome' : cs[findChild ks x]? = some (node cKeys cChildren) := hsome
have hval : insertCost t x (node ks cs)
= 1 + insertCost t x (node (cKeys.take (t - 1)) (cChildren.take t)) := by
rw [insertCost, if_neg hne]
dsimp only
split
· rename_i hcnone
rw [hsome'] at hcnone
simp at hcnone
· rename_i c hcsome
obtain rfl : c = node cKeys cChildren := by
rw [hsome'] at hcsome; injection hcsome with h; exact h.symm
dsimp only
rw [if_pos hfull, if_pos hlt]
rw [hval]
have hmem : node cKeys cChildren ∈ cs :=
List.mem_iff_getElem?.mpr ⟨findChild ks x, hsome'⟩
have hltH : heightOf (node (cKeys.take (t - 1)) (cChildren.take t)) <
heightOf (node ks cs) :=
lt_of_le_of_lt (heightOf_le_of_children_subset (List.take_subset _ _))
(heightOf_mem_lt hmem)
omega
| case4 ks cs hne i cKeys cChildren hsome hfull median hnlt hsome2 ih =>
have hsome' : cs[findChild ks x]? = some (node cKeys cChildren) := hsome
have hval : insertCost t x (node ks cs)
= 1 + insertCost t x (node (cKeys.drop t) (cChildren.drop t)) := by
rw [insertCost, if_neg hne]
dsimp only
split
· rename_i hcnone
rw [hsome'] at hcnone
simp at hcnone
· rename_i c hcsome
obtain rfl : c = node cKeys cChildren := by
rw [hsome'] at hcsome; injection hcsome with h; exact h.symm
dsimp only
rw [if_pos hfull, if_neg hnlt]
rw [hval]
have hmem : node cKeys cChildren ∈ cs :=
List.mem_iff_getElem?.mpr ⟨findChild ks x, hsome'⟩
have hltH : heightOf (node (cKeys.drop t) (cChildren.drop t)) <
heightOf (node ks cs) :=
lt_of_le_of_lt (heightOf_le_of_children_subset (List.drop_subset _ _))
(heightOf_mem_lt hmem)
omega
| case5 ks cs hne i cKeys cChildren hsome hnfull hsome2 ih =>
have hsome' : cs[findChild ks x]? = some (node cKeys cChildren) := hsome
have hval : insertCost t x (node ks cs)
= 1 + insertCost t x (node cKeys cChildren) := by
rw [insertCost, if_neg hne]
dsimp only
split
· rename_i hcnone
rw [hsome'] at hcnone
simp at hcnone
· rename_i c hcsome
obtain rfl : c = node cKeys cChildren := by
rw [hsome'] at hcsome; injection hcsome with h; exact h.symm
dsimp only
rw [if_neg hnfull]
rw [hval]
have hmem : node cKeys cChildren ∈ cs :=
List.mem_iff_getElem?.mpr ⟨findChild ks x, hsome'⟩
have hltH : heightOf (node cKeys cChildren) < heightOf (node ks cs) :=
heightOf_mem_lt hmem
omegaDeletion height bound
private lemma child_lt_node (ks : List Nat) {cs : List BTree} {j : Nat} {c : BTree}
(hc : cs[j]? = some c) : heightOf c < heightOf (node ks cs) :=
heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨j, hc⟩)
private lemma merge_lt_node (ks : List Nat) {cs : List BTree} {a b : BTree} (s : Nat)
(ha : a ∈ cs) (hb : b ∈ cs) :
heightOf (mergeNodes a s b) < heightOf (node ks cs) := by
rw [heightOf_mergeNodes_eq_max]
have hha : heightOf a < heightOf (node ks cs) := heightOf_mem_lt ha
have hhb : heightOf b < heightOf (node ks cs) := heightOf_mem_lt hb
omegaprivate lemma rotateLeft_target_lt_node (ks : List Nat) {cs : List BTree} {a b : BTree} (s : Nat)
(ha : a ∈ cs) (hb : b ∈ cs) :
heightOf (rotateLeft a s b).2.2 < heightOf (node ks cs) := by
have hle : heightOf (rotateLeft a s b).2.2 ≤ max (heightOf a) (heightOf b) :=
heightOf_rotateLeft_right_le a s b
have hha : heightOf a < heightOf (node ks cs) := heightOf_mem_lt ha
have hhb : heightOf b < heightOf (node ks cs) := heightOf_mem_lt hb
omegaprivate lemma rotateRight_target_lt_node (ks : List Nat) {cs : List BTree} {a b : BTree} (s : Nat)
(ha : a ∈ cs) (hb : b ∈ cs) :
heightOf (rotateRight a s b).1 < heightOf (node ks cs) := by
have hle : heightOf (rotateRight a s b).1 ≤ max (heightOf a) (heightOf b) :=
heightOf_rotateRight_left_le a s b
have hha : heightOf a < heightOf (node ks cs) := heightOf_mem_lt ha
have hhb : heightOf b < heightOf (node ks cs) := heightOf_mem_lt hb
omega
O(h) deletion. composedDelete descends at most one path, so its
number of descent charges is bounded by the tree height plus one.
theorem deleteCost_le_height (t x : Nat) (tr : BTree) :
deleteCost t x tr ≤ heightOf tr + 1 := by
induction x, tr using deleteCost.induct (t := t) with
| case1 x ks cs hleaf =>
rw [deleteCost, if_pos hleaf]
omega
| case2 ks cs hnonempty sep leftChild rightChild hleftReady i hpos ki hsep hleft hright ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hleft hright
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hleft, hright]
simp [hleftReady]
have hlt := child_lt_node ks hleft
omega
| case3 ks cs hnonempty sep leftChild rightChild hleftNotReady hrightReady i hpos ki hsep hleft hright ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hleft hright
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hleft, hright]
simp [hleftNotReady, hrightReady]
have hlt := child_lt_node ks hright
omega
| case4 ks cs hnonempty sep leftChild rightChild hleftNotReady hrightNotReady i hpos ki hsep hleft hright ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hleft hright
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hleft, hright]
simp [hleftNotReady, hrightNotReady]
have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨ki, hleft⟩)
(List.mem_iff_getElem?.mpr ⟨ki + 1, hright⟩)
omega
| case5 ks cs hnonempty sep leftChild i hpos ki hsep hleft hrightNone =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hleft hrightNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hleft, hrightNone]
simp
| case6 ks cs hnonempty sep i hpos ki hsep hleftNone =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hleftNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hleftNone]
simp
| case7 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildReady ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hchild
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchild]
simp [hne, hchildReady]
have hlt := child_lt_node ks hchild
omega
| case8 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibReady sep2 hsep2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hsep2
simp only [ki, i] at hsep
have hsepEq : sep = sep2 := Option.some.inj (hsep.symm.trans hsep2)
subst sep2
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchild, hleftSib]
simp [hne, hchildNotReady, hleftSibReady]
have hlt := rotateLeft_target_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i - 1, hleftSib⟩)
(List.mem_iff_getElem?.mpr ⟨i, hchild⟩)
omega
| case9 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibReady hsepNone ih =>
simp only [ki, i] at hsep hsepNone
rw [hsep] at hsepNone
cases hsepNone
| case10 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady sep2 hsep2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightSib hsep2
simp only [ki, i] at hsep
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchild, hleftSib, hrightSib, hsep2]
simp [hne, hchildNotReady, hleftSibNotReady, hrightSibReady]
have hlt := rotateRight_target_lt_node ks sep2 (List.mem_iff_getElem?.mpr ⟨i, hchild⟩)
(List.mem_iff_getElem?.mpr ⟨i + 1, hrightSib⟩)
omega
| case11 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady hsepNone ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightSib hsepNone
simp only [ki, i] at hsep
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchild, hleftSib, hrightSib, hsepNone]
simp [hne, hchildNotReady, hleftSibNotReady, hrightSibReady]
have hlt := child_lt_node ks hchild
omega
| case12 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady sep2 hsep2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightSib hsep2
simp only [ki, i] at hsep
have hsepEq : sep = sep2 := Option.some.inj (hsep.symm.trans hsep2)
subst sep2
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchild, hleftSib, hrightSib]
simp [hne, hchildNotReady, hleftSibNotReady, hrightSibNotReady]
have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i - 1, hleftSib⟩)
(List.mem_iff_getElem?.mpr ⟨i, hchild⟩)
omega
| case13 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady hsepNone ih =>
simp only [ki, i] at hsep hsepNone
rw [hsep] at hsepNone
cases hsepNone
| case14 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone sep2 hsep2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightNone hsep2
simp only [ki, i] at hsep
have hsepEq : sep = sep2 := Option.some.inj (hsep.symm.trans hsep2)
subst sep2
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchild, hleftSib, hrightNone]
simp [hne, hchildNotReady, hleftSibNotReady]
have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i - 1, hleftSib⟩)
(List.mem_iff_getElem?.mpr ⟨i, hchild⟩)
omega
| case15 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone hsepNone ih =>
simp only [ki, i] at hsep hsepNone
rw [hsep] at hsepNone
cases hsepNone
| case16 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady hleftNone ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hchild hleftNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchild, hleftNone]
simp [hne, hchildNotReady]
have hlt := child_lt_node ks hchild
omega
| case17 x ks cs hnonempty i hpos ki sep hsep hne hchildNone =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsep hchildNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsep, hchildNone]
simp [hne]
| case18 x ks cs hnonempty i hpos ki hsepNone child hchild hchildReady ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsepNone hchild
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchild]
simp [hchildReady]
have hlt := child_lt_node ks hchild
omega
| case19 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibReady sep hsep ih =>
simp only [i] at hsep
simp only [ki, i] at hsepNone
rw [hsepNone] at hsep
cases hsep
| case20 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibReady hsepNone2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib
simp only [ki, i] at hsepNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchild, hleftSib]
simp [hchildNotReady, hleftSibReady]
have hlt := child_lt_node ks hchild
omega
| case21 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady sep hsep ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightSib hsep
simp only [ki, i] at hsepNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchild, hleftSib, hrightSib, hsep]
simp [hchildNotReady, hleftSibNotReady, hrightSibReady]
have hlt := rotateRight_target_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i, hchild⟩)
(List.mem_iff_getElem?.mpr ⟨i + 1, hrightSib⟩)
omega
| case22 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady hsepNone2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightSib hsepNone2
simp only [ki, i] at hsepNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchild, hleftSib, hrightSib, hsepNone2]
simp [hchildNotReady, hleftSibNotReady, hrightSibReady]
have hlt := child_lt_node ks hchild
omega
| case23 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady sep hsep ih =>
simp only [i] at hsep
simp only [ki, i] at hsepNone
rw [hsepNone] at hsep
cases hsep
| case24 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady hsepNone2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightSib
simp only [ki, i] at hsepNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchild, hleftSib, hrightSib]
simp [hchildNotReady, hleftSibNotReady, hrightSibNotReady]
have hlt := child_lt_node ks hchild
omega
| case25 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone sep hsep ih =>
simp only [i] at hsep
simp only [ki, i] at hsepNone
rw [hsepNone] at hsep
cases hsep
| case26 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone hsepNone2 ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftSib hrightNone
simp only [ki, i] at hsepNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchild, hleftSib, hrightNone]
simp [hchildNotReady, hleftSibNotReady]
have hlt := child_lt_node ks hchild
omega
| case27 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady hleftNone ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos hchild hleftNone
simp only [ki, i] at hsepNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchild, hleftNone]
simp [hchildNotReady]
have hlt := child_lt_node ks hchild
omega
| case28 x ks cs hnonempty i hpos ki hsepNone hchildNone =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hpos
simp only [ki, i] at hsepNone hchildNone
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos]
rw [hsepNone, hchildNone]
simp
| case29 x ks cs hnonempty i hnotPos child hchild hchildReady ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hnotPos
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte]
rw [dif_neg hnotPos]
rw [hchild]
simp [hchildReady]
have hlt := child_lt_node ks hchild
omega
| case30 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibReady sep hsep ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hnotPos
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte]
rw [dif_neg hnotPos]
rw [hchild, hrightSib, hsep]
simp [hchildNotReady, hrightSibReady]
have hlt := rotateRight_target_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨0, hchild⟩)
(List.mem_iff_getElem?.mpr ⟨1, hrightSib⟩)
omega
| case31 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibReady hsepNone ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hnotPos
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte]
rw [dif_neg hnotPos]
rw [hchild, hrightSib, hsepNone]
simp [hchildNotReady, hrightSibReady]
have hlt := child_lt_node ks hchild
omega
| case32 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibNotReady sep hsep ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hnotPos
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte]
rw [dif_neg hnotPos]
rw [hchild, hrightSib, hsep]
simp [hchildNotReady, hrightSibNotReady]
have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨0, hchild⟩)
(List.mem_iff_getElem?.mpr ⟨1, hrightSib⟩)
omega
| case33 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibNotReady hsepNone ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hnotPos
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte]
rw [dif_neg hnotPos]
rw [hchild, hrightSib, hsepNone]
simp [hchildNotReady, hrightSibNotReady]
have hlt := child_lt_node ks hchild
omega
| case34 x ks cs hnonempty i hnotPos child hchild hchildNotReady hrightNone ih =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hnotPos
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte]
rw [dif_neg hnotPos]
rw [hchild, hrightNone]
simp [hchildNotReady]
have hlt := child_lt_node ks hchild
omega
| case35 x ks cs hnonempty i hnotPos hchildNone =>
have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty
simp only [i] at hnotPos
rw [deleteCost]
simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte]
rw [dif_neg hnotPos]
rw [hchildNone]
simpTop-level insertion cost
Insertion descent budget with one extra charge for the full-root split. The charge is a budget unit, not a literal count of split-page reads/writes.
def insertRootCost (t x : Nat) (tr : BTree) : Nat :=
if rootKeyCount tr = 2 * t - 1 then insertCost t x (splitRoot t tr) + 1
else insertCost t x tr
O(h) top-level insertion. Splitting a full root adds exactly one
level and then insertNonFull descends at most one path, so the cost is
bounded by the tree height plus three.
theorem insertRootCost_le_height (t x : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr) :
insertRootCost t x tr ≤ heightOf tr + 3 := by
unfold insertRootCost
by_cases hfull : rootKeyCount tr = 2 * t - 1
· rw [if_pos hfull]
have hsplit : heightOf (splitRoot t tr) = heightOf tr + 1 :=
splitRoot_height t ht hwf hfull
have hins := insertCost_le_height t x (splitRoot t tr)
omega
· rw [if_neg hfull]
have hins := insertCost_le_height t x tr
omegaLogarithmic descent-budget bounds
Common logarithmic envelope for the descent budgets. Its historical
diskAccessBound name is retained, but a full page-I/O interpretation
requires additional accounting not established in this module.
def diskAccessBound (t : Nat) (n : Nat) : Nat := Nat.log t ((n + 1) / 2) + 3
Search has at most log_t n + O(1) descent charges on a well-formed
tree.
theorem searchCost_le_diskAccessBound (t : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr) (x : Nat) :
searchCost x tr ≤ diskAccessBound t (totalKeys tr) := by
have hh : heightOf tr ≤ Nat.log t ((totalKeys tr + 1) / 2) :=
wellFormed_height_log_bound t ht hwf
have hc := searchCost_le_height x tr
unfold diskAccessBound
omega
Top-level insertion has at most log_t n + O(1) descent/root-split charges on a
well-formed tree.
theorem insertRootCost_le_diskAccessBound (t : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr) (x : Nat) :
insertRootCost t x tr ≤ diskAccessBound t (totalKeys tr) := by
have hh : heightOf tr ≤ Nat.log t ((totalKeys tr + 1) / 2) :=
wellFormed_height_log_bound t ht hwf
have hc := insertRootCost_le_height t x ht hwf
unfold diskAccessBound
omega
Deletion has at most log_t n + O(1) descent charges on a well-formed
tree.
theorem deleteCost_le_diskAccessBound (t : Nat) (ht : 2 ≤ t) {tr : BTree}
(hwf : WellFormed t tr) (x : Nat) :
deleteCost t x tr ≤ diskAccessBound t (totalKeys tr) := by
have hh : heightOf tr ≤ Nat.log t ((totalKeys tr + 1) / 2) :=
wellFormed_height_log_bound t ht hwf
have hc := deleteCost_le_height t x tr
unfold diskAccessBound
omega
The common descent-budget envelope is O(log_t n). This is an
asymptotic theorem about the displayed numeric bound, not an additional
operational refinement for page I/O or auxiliary traversals.
theorem diskAccessBound_isBigO_log_t (t : Nat) (ht : 2 ≤ t) :
CLRS.Chapter03.isBigO (fun n => (diskAccessBound t n : ℝ))
(fun n => (Nat.log t n : ℝ)) := by
rw [CLRS.Chapter03.isBigO_iff]
refine ⟨4, by norm_num, t, ?_⟩
intro n hn
have hmono : Nat.log t ((n + 1) / 2) ≤ Nat.log t n := by
apply Nat.log_mono_right
omega
have hlogpos : 0 < Nat.log t n := Nat.log_pos (by omega : 1 < t) (by omega : t ≤ n)
unfold diskAccessBound
have h1 : (Nat.log t ((n + 1) / 2) + 3 : ℝ) ≤ 4 * (Nat.log t n : ℝ) := by
have h2 : (Nat.log t ((n + 1) / 2) : ℝ) ≤ (Nat.log t n : ℝ) := by
exact_mod_cast hmono
have h3 : (3 : ℝ) ≤ 3 * (Nat.log t n : ℝ) := by
have h4 : (1 : ℝ) ≤ (Nat.log t n : ℝ) := by
exact_mod_cast (Nat.succ_le_of_lt hlogpos)
nlinarith
nlinarith
have h_nonneg_left : 0 ≤ ((Nat.log t ((n + 1) / 2) + 3 : Nat) : ℝ) := by positivity
have h_nonneg_right : 0 ≤ (Nat.log t n : ℝ) := by positivity
rw [abs_of_nonneg h_nonneg_left, abs_of_nonneg h_nonneg_right]
push_cast
exact h1end BTreeend Chapter18end CLRSCLRSLean.FourthEdition.Chapter_18.Section_18_1_B_Tree_Model.Search
CLRS Section 18.1 - Separator-guided B-tree search
Defines the child-selection function used by B-tree search and insertion, reusable path-localization and height lemmas, and a total executable search that descends through exactly one separator-selected child.
Main results:
-
findChild_localizes_mem: localizes a non-separator member to the selected child under sorted and child-bounded node invariants. -
searchExec: checks the current node and otherwise follows only the separator-selected child. -
searchExec_sound: successful executable search implies membership without structural assumptions. -
searchExec_complete: sorted, child-bounded trees expose every member along the selected path. -
searchExec_true_iff: on sorted, child-bounded trees, characterizes successful executable search by membership. -
searchExec_eq_search: on sorted, child-bounded trees, connects executable search to the imported specification oraclesearch.
The selected-child localization and routing wrappers are used by the proved exact erase-one semantics for executable deletion.
namespace CLRS.Chapter18.BTreeopen ListChild selection
Index of the child that key x descends into: the number of leading keys
≤ x (correct for a sorted key list).
def findChild : List Nat → Nat → Nat
| [], _ => 0
| k :: ks, x => if k ≤ x then findChild ks x + 1 else 0Height lemmas
foldl max never drops below its accumulator.
lemma foldl_max_ge (b : Nat) (l : List Nat) : b ≤ l.foldl max b := by
induction l generalizing b with
| nil => simp
| cons y ys ih =>
simp only [List.foldl_cons]
exact le_trans (le_max_left b y) (ih (max b y))
Every element of l is ≤ l.foldl max b.
lemma mem_le_foldl_max : ∀ {l : List Nat} {a b : Nat}, a ∈ l → a ≤ l.foldl max b := by
intro l
induction l with
| nil => intro a b h; simp at h
| cons y ys ih =>
intro a b h
simp only [List.foldl_cons]
rcases List.mem_cons.mp h with rfl | h
· exact le_trans (le_max_right b a) (foldl_max_ge (max b a) ys)
· exact ih h
If every element of l is ≤ M and b ≤ M, then l.foldl max b ≤ M.
lemma foldl_max_le' : ∀ {l : List Nat} {M b : Nat}, b ≤ M → (∀ a ∈ l, a ≤ M) → l.foldl max b ≤ M := by
intro l
induction l with
| nil => intro M b hb _; simpa using hb
| cons y ys ih =>
intro M b hb h
simp only [List.foldl_cons]
exact ih (max_le hb (h y (by simp))) (fun a ha => h a (by simp [ha]))
Folding max over the heights of a sub-multiset of children is ≤ folding
over the full children list.
lemma foldl_max_heightOf_subset {cs' cs : List BTree} (h : cs' ⊆ cs) :
(cs'.map heightOf).foldl max 0 ≤ (cs.map heightOf).foldl max 0 := by
apply foldl_max_le' (foldl_max_ge 0 _)
intro a ha
rw [List.mem_map] at ha
obtain ⟨c, hc, rfl⟩ := ha
exact mem_le_foldl_max (List.mem_map_of_mem (h hc))A child is strictly shorter than its parent.
lemma heightOf_mem_lt {ks : List Nat} {children : List BTree} {c : BTree}
(hc : c ∈ children) : heightOf c < heightOf (node ks children) := by
cases children with
| nil => simp at hc
| cons d ds =>
have hle := mem_le_foldl_max (a := heightOf c) (b := 0) (List.mem_map_of_mem hc)
simp only [heightOf]
omegaReplacing the children of a node by a sub-multiset cannot increase the height.
lemma heightOf_le_of_children_subset {a b : List Nat} {cs' cs : List BTree}
(h : cs' ⊆ cs) : heightOf (node a cs') ≤ heightOf (node b cs) := by
cases cs' with
| nil => simp [heightOf]
| cons d ds =>
cases cs with
| nil => exact absurd (h List.mem_cons_self) (by simp)
| cons e es =>
have hsub := foldl_max_heightOf_subset (cs' := d :: ds) (cs := e :: es) h
simp only [heightOf]
omegaChild-index bounds and range correctness
findChild never exceeds the number of keys, so on a node with
children.length = keys.length + 1 it always indexes a real child.
lemma findChild_le (ks : List Nat) (x : Nat) : findChild ks x ≤ ks.length := by
induction ks with
| nil => simp [findChild]
| cons k ks ih =>
unfold findChild
split
· simp only [List.length_cons]; omega
· omega
Every key before the chosen child index is ≤ x.
lemma findChild_take_le (x : Nat) : ∀ (ks : List Nat), ∀ k ∈ ks.take (findChild ks x), k ≤ x := by
intro ks
induction ks with
| nil => intro k hk; simp at hk
| cons a as ih =>
intro k hk
rw [findChild] at hk
split at hk
· rename_i hax
rw [List.take_succ_cons] at hk
rcases List.mem_cons.mp hk with rfl | hk
· exact hax
· exact ih k hk
· simp at hk
On a sorted key list, every key from the chosen child index onward is > x.
lemma findChild_drop_gt (x : Nat) : ∀ {ks : List Nat}, List.Pairwise (· ≤ ·) ks →
∀ k ∈ ks.drop (findChild ks x), x < k := by
intro ks
induction ks with
| nil => intro _ k hk; simp at hk
| cons a as ih =>
intro hs k hk
have hsa : ∀ b ∈ as, a ≤ b := (List.pairwise_cons.mp hs).1
have hs' : List.Pairwise (· ≤ ·) as := (List.pairwise_cons.mp hs).2
rw [findChild] at hk
split at hk
· rename_i hax
rw [List.drop_succ_cons] at hk
exact ih hs' k hk
· rename_i hax
have hxa : x < a := not_le.mp hax
simp only [List.drop_zero, List.mem_cons] at hk
rcases hk with rfl | hk
· exact hxa
· exact lt_of_lt_of_le hxa (hsa k hk)
The right separator at the chosen child bounds x from above (sorted keys).
lemma findChild_x_hi {ks : List Nat} (hs : List.Pairwise (· ≤ ·) ks) (x : Nat) :
∀ hi, ks[findChild ks x]? = some hi → x ≤ hi := by
intro hi hhi
have hmem : hi ∈ ks.drop (findChild ks x) := by
rw [List.mem_iff_getElem?]
exact ⟨0, by rw [List.getElem?_drop, Nat.add_zero]; exact hhi⟩
exact le_of_lt (findChild_drop_gt x hs hi hmem)
The left separator at the chosen child bounds x from below.
lemma findChild_x_lo (ks : List Nat) (x : Nat) :
findChild ks x = 0 ∨ ∀ lo, ks[findChild ks x - 1]? = some lo → lo ≤ x := by
rcases Nat.eq_zero_or_pos (findChild ks x) with h0 | hpos
· exact Or.inl h0
· right
intro lo hlo
have hmem : lo ∈ ks.take (findChild ks x) := by
rw [List.mem_iff_getElem?]
exact ⟨findChild ks x - 1, by rw [List.getElem?_take_of_lt (by omega)]; exact hlo⟩
exact findChild_take_le x ks lo hmem
If a key absent from a sorted node's separators occurs in one of its children,
that child is exactly the one selected by findChild.
theorem findChild_localizes_mem
{ks : List Nat} {cs : List BTree} {x j : Nat} {child : BTree}
(hsorted : List.Pairwise (· ≤ ·) ks)
(hbounded : ChildBounded (node ks cs))
(hxkeys : x ∉ ks)
(hchild : cs[j]? = some child)
(hxchild : x ∈ keysOf child) :
j = findChild ks x := by
have hjcs : j < cs.length :=
_root_.of_getElem?_eq_some (c := cs) (i := j) hchild
have hchild_get : cs.get ⟨j, hjcs⟩ = child :=
(_root_.getElem?_eq_some_iff.mp hchild).choose_spec
unfold ChildBounded at hbounded
rcases hbounded with ⟨hshape, hbounds, _⟩
have hlength : cs.length = ks.length + 1 := by
rcases hshape with hempty | hlength
· have : cs.length = 0 := by simpa using hempty
omega
· exact hlength
have hjbounds := hbounds j hjcs
dsimp only at hjbounds
rw [hchild_get] at hjbounds
have hnot_left : ¬j < findChild ks x := by
intro hjleft
have hjks : j < ks.length := by
have := findChild_le ks x
omega
have hsep_mem : ks[j] ∈ ks.take (findChild ks x) := by
rw [List.mem_iff_getElem?]
refine ⟨j, ?_⟩
rw [List.getElem?_take_of_lt hjleft, List.getElem?_eq_getElem hjks]
have hsep_le : ks[j] ≤ x :=
findChild_take_le x ks ks[j] hsep_mem
have hkey_le : x ≤ ks[j] := by
have hupper := hjbounds.2
simp only [List.getElem?_eq_getElem hjks] at hupper
exact hupper x hxchild
apply hxkeys
rw [show x = ks[j] by omega]
exact List.getElem_mem hjks
have hnot_right : ¬findChild ks x < j := by
intro hjright
have hjpos : 0 < j := by omega
have hjpred : j - 1 < ks.length := by omega
have hsep_mem : ks[j - 1] ∈ ks.drop (findChild ks x) := by
rw [List.mem_iff_getElem?]
refine ⟨j - 1 - findChild ks x, ?_⟩
rw [List.getElem?_drop]
have hindex :
findChild ks x + (j - 1 - findChild ks x) = j - 1 := by
omega
rw [hindex, List.getElem?_eq_getElem hjpred]
have hx_lt : x < ks[j - 1] :=
findChild_drop_gt x hsorted (ks[j - 1]) hsep_mem
have hsep_le : ks[j - 1] ≤ x := by
rcases hjbounds.1 with hjzero | hlower
· omega
· simp only [List.getElem?_eq_getElem hjpred] at hlower
exact hlower x hxchild
omega
omegaIf a key occurs among sorted separators, the selected child index is positive and its predecessor separator is that key.
theorem findChild_pos_and_pred_eq_of_mem
{ks : List Nat} {x : Nat}
(hsorted : List.Pairwise (· ≤ ·) ks)
(hx : x ∈ ks) :
0 < findChild ks x ∧
ks[findChild ks x - 1]? = some x := by
induction ks with
| nil => simp at hx
| cons a as ih =>
have hsortedTail : List.Pairwise (· ≤ ·) as :=
(List.pairwise_cons.mp hsorted).2
have ha_le : a ≤ x := by
rcases List.mem_cons.mp hx with hxa | hxTail
· omega
· exact (List.pairwise_cons.mp hsorted).1 x hxTail
rw [findChild, if_pos ha_le]
refine ⟨by omega, ?_⟩
simp only [Nat.add_sub_cancel]
by_cases hxTail : x ∈ as
· obtain ⟨hpos, hpred⟩ := ih hsortedTail hxTail
cases hfind : findChild as x with
| zero => omega
| succ j =>
simp only [hfind, Nat.succ_sub_one] at hpred
simpa [hfind] using hpred
· have hxa : x = a :=
(List.mem_cons.mp hx).resolve_right hxTail
subst x
have hzero : findChild as a = 0 := by
cases as with
| nil => rfl
| cons b bs =>
have hab : a ≤ b :=
(List.pairwise_cons.mp hsorted).1 b (by simp)
have hne : b ≠ a := by
intro hba
apply hxTail
simp [hba]
have hnot : ¬b ≤ a := by omega
simp [findChild, hnot]
simp [hzero]A non-selected child of a sorted, child-bounded node cannot contain a key that is absent from the node's separators.
theorem findChild_not_mem_child_of_ne
{ks : List Nat} {cs : List BTree} {x j : Nat} {child : BTree}
(hsorted : List.Pairwise (· ≤ ·) ks)
(hbounded : ChildBounded (node ks cs))
(hxkeys : x ∉ ks)
(hchild : cs[j]? = some child)
(hne : j ≠ findChild ks x) :
x ∉ keysOf child := by
intro hxchild
exact hne
(findChild_localizes_mem hsorted hbounded hxkeys hchild hxchild)If a non-separator key belongs to a sorted, child-bounded node, it belongs to the selected child.
theorem findChild_selected_child_mem
{ks : List Nat} {cs : List BTree} {x : Nat} {child : BTree}
(hsorted : List.Pairwise (· ≤ ·) ks)
(hbounded : ChildBounded (node ks cs))
(hxkeys : x ∉ ks)
(hchild : cs[findChild ks x]? = some child)
(hx : x ∈ keysOf (node ks cs)) :
x ∈ keysOf child := by
unfold keysOf at hx
rw [List.mem_append, List.mem_flatMap] at hx
rcases hx with hxnode | ⟨descendant, hdescendant, hxdescendant⟩
· exact (hxkeys hxnode).elim
· obtain ⟨j, hj⟩ := List.mem_iff_getElem?.mp hdescendant
have hjfind : j = findChild ks x :=
findChild_localizes_mem hsorted hbounded hxkeys hj hxdescendant
rw [hjfind, hchild] at hj
cases hj
exact hxdescendantExecutable search
Separator-guided executable B-tree search. The current node is checked first;
on a miss, search continues only in the child selected by findChild.
def searchExec (x : Nat) : BTree → Bool
| node ks cs =>
if x ∈ ks then
true
else
match _hc : cs[findChild ks x]? with
| some child => searchExec x child
| none => false
termination_by tr => heightOf tr
decreasing_by
exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨findChild ks x, _hc⟩)Executable search is sound on every B-tree: a successful result witnesses membership in the tree, without requiring structural invariants.
theorem searchExec_sound {x : Nat} {tr : BTree}
(hsearch : searchExec x tr = true) : mem x tr := by
revert hsearch
induction tr using searchExec.induct x with
| case1 ks cs hxkeys =>
intro _
unfold mem keysOf
exact List.mem_append_left _ hxkeys
| case2 ks cs hxkeys child hchild ih =>
intro hsearch
have hchild_search : searchExec x child = true := by
rw [searchExec, if_neg hxkeys] at hsearch
split at hsearch
· rename_i child' hchild'
rw [hchild] at hchild'
cases hchild'
exact hsearch
· rename_i hnone
rw [hchild] at hnone
contradiction
have hchild_mem : x ∈ keysOf child := ih hchild_search
unfold mem keysOf
rw [List.mem_append, List.mem_flatMap]
right
exact ⟨child, List.mem_iff_getElem?.mpr ⟨findChild ks x, hchild⟩, hchild_mem⟩
| case3 ks cs hxkeys hchild =>
intro hsearch
rw [searchExec, if_neg hxkeys] at hsearch
split at hsearch
· rename_i child hsome
rw [hchild] at hsome
contradiction
· simp at hsearchOn sorted, child-bounded B-trees, executable search is complete: every member is found by the separator-selected descent path.
theorem searchExec_complete {x : Nat} {tr : BTree}
(hsorted : Sorted tr) (hbounded : ChildBounded tr)
(hmem : mem x tr) : searchExec x tr = true := by
revert hsorted hbounded hmem
induction tr using searchExec.induct x with
| case1 ks cs hxkeys =>
intro _ _ _
rw [searchExec, if_pos hxkeys]
| case2 ks cs hxkeys child hchild ih =>
intro hsorted hbounded hmem
unfold Sorted at hsorted
rcases hsorted with ⟨hkeys_sorted, hchildren_sorted⟩
unfold mem keysOf at hmem
rw [List.mem_append, List.mem_flatMap] at hmem
rcases hmem with hxnode | ⟨descendant, hdescendant, hxdescendant⟩
· exact (hxkeys hxnode).elim
· obtain ⟨j, hj⟩ := List.mem_iff_getElem?.mp hdescendant
have hjfind : j = findChild ks x :=
findChild_localizes_mem hkeys_sorted hbounded hxkeys hj hxdescendant
subst j
rw [hchild] at hj
cases hj
have hchild_mem : child ∈ cs :=
List.mem_iff_getElem?.mpr ⟨findChild ks x, hchild⟩
have hchild_sorted : Sorted child :=
hchildren_sorted child hchild_mem
have hchild_bounded : ChildBounded child := by
unfold ChildBounded at hbounded
exact hbounded.2.2 child hchild_mem
have hchild_search : searchExec x child = true :=
ih hchild_sorted hchild_bounded hxdescendant
rw [searchExec, if_neg hxkeys]
split
· rename_i child' hchild'
rw [hchild] at hchild'
cases hchild'
exact hchild_search
· rename_i hnone
rw [hchild] at hnone
contradiction
| case3 ks cs hxkeys hchild =>
intro hsorted hbounded hmem
unfold Sorted at hsorted
rcases hsorted with ⟨hkeys_sorted, _⟩
unfold mem keysOf at hmem
rw [List.mem_append, List.mem_flatMap] at hmem
rcases hmem with hxnode | ⟨descendant, hdescendant, hxdescendant⟩
· exact (hxkeys hxnode).elim
· obtain ⟨j, hj⟩ := List.mem_iff_getElem?.mp hdescendant
have hjfind : j = findChild ks x :=
findChild_localizes_mem hkeys_sorted hbounded hxkeys hj hxdescendant
rw [hjfind, hchild] at hj
contradictionOn sorted, child-bounded trees, executable search returns true exactly for members of the tree.
theorem searchExec_true_iff {x : Nat} {tr : BTree}
(hsorted : Sorted tr) (hbounded : ChildBounded tr) :
searchExec x tr = true ↔ mem x tr :=
⟨searchExec_sound, searchExec_complete hsorted hbounded⟩
On sorted, child-bounded trees, separator-guided executable search agrees with
the specification-level membership oracle search.
theorem searchExec_eq_search {x : Nat} {tr : BTree}
(hsorted : Sorted tr) (hbounded : ChildBounded tr) :
searchExec x tr = search x tr := by
apply Bool.eq_iff_iff.mpr
exact (searchExec_true_iff hsorted hbounded).trans (search_true_iff x tr).symmend CLRS.Chapter18.BTree