Invariant contracts for composed B-tree deletion
This module records the invariant packet required by recursive deletion and
the one-step normalization used after deleting from the root. The raw
composedDelete operation may temporarily produce an empty root with one
child; normalizeRoot contracts exactly that shape.
namespace CLRSnamespace Chapter18namespace BTreeBundled deletion contracts
The four structural invariants required at a node during deletion.
def NodeWF (t : Nat) (isRoot : Bool) (tr : BTree) : Prop :=
Sorted tr ∧ ChildBounded tr ∧ Occupancy t isRoot tr ∧ SameDepth tr
The entry guard for deletion: roots are always ready, while non-root nodes
must contain at least t keys before recursive descent.
Every key represented after an operation was represented before it.
The structural result permitted from raw root deletion. It is either an
ordinary occupied root or the single-child empty-root transient contracted by
normalizeRoot.
def RootDeleteResult (t : Nat) (tr : BTree) : Prop :=
Sorted tr ∧ ChildBounded tr ∧ SameDepth tr ∧
(Occupancy t true tr ∨
∃ child, tr = node [] [child] ∧ Occupancy t false child)namespace NodeWFProject sortedness from the deletion invariant packet.
Project child bounds from the deletion invariant packet.
theorem childBounded {t : Nat} {isRoot : Bool} {tr : BTree}
(h : NodeWF t isRoot tr) : ChildBounded tr :=
h.2.1Project occupancy from the deletion invariant packet.
theorem occupancy {t : Nat} {isRoot : Bool} {tr : BTree}
(h : NodeWF t isRoot tr) : Occupancy t isRoot tr :=
h.2.2.1Project equal leaf depth from the deletion invariant packet.
theorem sameDepth {t : Nat} {isRoot : Bool} {tr : BTree}
(h : NodeWF t isRoot tr) : SameDepth tr :=
h.2.2.2end NodeWFnamespace WellFormedA well-formed tree is the root-specialized deletion invariant packet.
theorem nodeWF {t : Nat} {tr : BTree}
(h : WellFormed t tr) : NodeWF t true tr :=
hend WellFormedRoot calls always satisfy the deletion-entry guard.
theorem deleteReady_root (t : Nat) (tr : BTree) :
DeleteReady t true tr := by
simp [DeleteReady]
At a non-root node, readiness is exactly the CLRS t-key guard.
theorem deleteReady_nonRoot_iff (t : Nat) (tr : BTree) :
DeleteReady t false tr ↔ t ≤ numKeys tr := by
simp [DeleteReady]Non-root occupancy implies root occupancy when the minimum degree is at least two.
theorem occupancy_true_of_false {t : Nat} {tr : BTree}
(ht : 2 ≤ t) (h : Occupancy t false tr) :
Occupancy t true tr := by
rcases tr with ⟨ks, cs⟩
unfold Occupancy at h ⊢
simp only [Bool.false_eq_true, ↓reduceIte] at h
obtain ⟨hlower, hupper, hchildren, hrec⟩ := h
have hkeys : 1 ≤ ks.length := by omega
have hchildrenRoot :
cs.isEmpty ∨ (2 ≤ cs.length ∧ cs.length ≤ 2 * t) := by
rcases hchildren with hleaf | ⟨hlowerChildren, hupperChildren⟩
· exact Or.inl hleaf
· exact Or.inr ⟨by omega, hupperChildren⟩
simp only [↓reduceIte]
refine ⟨?_, hupper, hchildrenRoot, hrec⟩
by_cases hempty : ks = [] ∧ cs = []
· simp [hempty]
· simpa [hempty] using hkeysEvery bundled invariant packet can be viewed through the weaker root occupancy contract when the minimum degree is at least two.
theorem NodeWF.asRoot {t : Nat} {isRoot : Bool} {tr : BTree}
(h : NodeWF t isRoot tr) (ht : 2 ≤ t) :
NodeWF t true tr := by
cases isRoot with
| false =>
exact
⟨h.sorted, h.childBounded,
occupancy_true_of_false ht h.occupancy, h.sameDepth⟩
| true =>
simpa using hRoot normalization
Contract an empty root with exactly one child; leave every other tree unchanged.
Run raw composed deletion and then contract its possible empty root.
def composedDeleteRoot (t x : Nat) (tr : BTree) : BTree :=
normalizeRoot (composedDelete t x tr)Root normalization preserves the represented key list exactly.
theorem keysOf_normalizeRoot (tr : BTree) :
keysOf (normalizeRoot tr) = keysOf tr := by
rcases tr with ⟨ks, cs⟩
cases ks with
| nil =>
cases cs with
| nil => rfl
| cons child rest =>
cases rest with
| nil => simp [normalizeRoot, keysOf]
| cons child₂ rest => rfl
| cons k ks => rflRoot normalization either preserves height or removes exactly the old root level.
theorem heightOf_normalizeRoot (tr : BTree) :
heightOf (normalizeRoot tr) = heightOf tr ∨
heightOf (normalizeRoot tr) + 1 = heightOf tr := by
rcases tr with ⟨ks, cs⟩
cases ks with
| nil =>
cases cs with
| nil => exact Or.inl rfl
| cons child rest =>
cases rest with
| nil =>
right
simp [normalizeRoot, heightOf, Nat.add_comm]
| cons child₂ rest => exact Or.inl rfl
| cons k ks => exact Or.inl rflprivate theorem normalizeRoot_eq_self_of_occupancy_true
{t : Nat} {tr : BTree} (h : Occupancy t true tr) :
normalizeRoot tr = tr := by
rcases tr with ⟨ks, cs⟩
cases ks with
| nil =>
cases cs with
| nil => rfl
| cons child rest =>
cases rest with
| nil => simp [Occupancy] at h
| cons child₂ rest => rfl
| cons k ks => rflNormalizing either allowed raw-root result produces a genuinely well-formed B-tree root.
theorem normalizeRoot_wellFormed {t : Nat} {tr : BTree}
(ht : 2 ≤ t) (h : RootDeleteResult t tr) :
WellFormed t (normalizeRoot tr) := by
obtain ⟨hsorted, hbounded, hdepth, hroot⟩ := h
rcases hroot with hoccupancy | ⟨child, htr, hchildOccupancy⟩
· rw [normalizeRoot_eq_self_of_occupancy_true hoccupancy]
exact ⟨hsorted, hbounded, hoccupancy, hdepth⟩
· subst tr
unfold Sorted at hsorted
unfold ChildBounded at hbounded
have hchildSorted : Sorted child :=
hsorted.2 child (by simp)
have hchildBounded : ChildBounded child :=
hbounded.2.2 child (by simp)
have hchildDepth : SameDepth child :=
sameDepth_children_sd hdepth child (by simp)
change WellFormed t child
exact ⟨hchildSorted, hchildBounded,
occupancy_true_of_false ht hchildOccupancy, hchildDepth⟩Recursive-descent lookup and guard helpers
Every strict in-range list index has a concrete getElem? witness.
theorem getElem?_exists_of_lt {α : Type*} {xs : List α} {i : Nat}
(hi : i < xs.length) :
∃ a, xs[i]? = some a :=
⟨xs[i], List.getElem?_eq_getElem hi⟩A positive index bounded by the list length has an in-range predecessor.
theorem getElem?_pred_exists {α : Type*} {xs : List α} {i : Nat}
(hpos : 0 < i) (hi : i ≤ xs.length) :
∃ a, xs[i - 1]? = some a :=
getElem?_exists_of_lt (by omega)If an indexed list element exists and its index is positive, its immediate left sibling exists.
theorem getElem?_leftSibling_exists {α : Type*} {xs : List α} {i : Nat} {a : α}
(hcurrent : xs[i]? = some a) (hpos : 0 < i) :
∃ left, xs[i - 1]? = some left := by
have hi : i < xs.length := (List.getElem?_eq_some_iff.mp hcurrent).1
exact getElem?_pred_exists hpos (Nat.le_of_lt hi)If index plus one is below the list length, the immediate right sibling exists.
theorem getElem?_rightSibling_exists {α : Type*} {xs : List α} {i : Nat}
(hi : i + 1 < xs.length) :
∃ right, xs[i + 1]? = some right :=
getElem?_exists_of_lt hiA positive child-search index always has a concrete separator immediately before it.
theorem findChild_predecessor_exists {ks : List Nat} {x : Nat}
(hpos : 0 < findChild ks x) :
∃ sep, ks[findChild ks x - 1]? = some sep :=
getElem?_pred_exists hpos (findChild_le ks x)
The predecessor-separator fallback of composedDelete is unreachable
at a positive child-search index.
theorem findChild_predecessor_none_absurd {ks : List Nat} {x : Nat}
(hpos : 0 < findChild ks x)
(hnone : ks[findChild ks x - 1]? = none) :
False := by
obtain ⟨sep, hsep⟩ := findChild_predecessor_exists hpos
rw [hnone] at hsep
simp at hsepnamespace NodeWFEvery child of a node satisfying the bundled invariant packet satisfies the same packet as a non-root node.
theorem child {t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
{child : BTree} (h : NodeWF t isRoot (node ks cs)) (hchild : child ∈ cs) :
NodeWF t false child := by
have hsorted := h.sorted
have hbounded := h.childBounded
have hoccupancy := h.occupancy
unfold Sorted at hsorted
unfold ChildBounded at hbounded
unfold Occupancy at hoccupancy
refine ⟨hsorted.2 child hchild, hbounded.2.2 child hchild,
hoccupancy.2.2.2 child hchild, ?_⟩
exact sameDepth_children_sd h.sameDepth child hchildAn occupied, nonempty bundled node has at least one key at every descendant when the minimum degree is at least two.
theorem allKeysPos {t : Nat} {isRoot : Bool} {tr : BTree}
(h : NodeWF t isRoot tr) (ht : 2 ≤ t) (hne : 0 < numKeys tr) :
AllKeysPos tr :=
allKeysPos_of_occupancy t ht tr isRoot h.occupancy hneA non-root bundled node automatically has at least one key at every descendant when the minimum degree is at least two.
theorem nonRoot_allKeysPos {t : Nat} {tr : BTree}
(h : NodeWF t false tr) (ht : 2 ≤ t) : AllKeysPos tr := by
apply h.allKeysPos ht
rcases tr with ⟨ks, cs⟩
have hlower : t - 1 ≤ ks.length := (occupancy_false_dest h.occupancy).1
show 0 < ks.length
omegaThe children of a bundled node are either absent or number exactly one more than its keys.
theorem children_rel {t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) :
cs = [] ∨ cs.length = ks.length + 1 :=
childBounded_children_rel h.childBounded
On an internal bundled node, findChild always selects an in-range child.
theorem findChild_lt {t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) (hchildren : cs ≠ []) (x : Nat) :
findChild ks x < cs.length := by
have hlength : cs.length = ks.length + 1 :=
h.children_rel.resolve_left hchildren
have hfind : findChild ks x ≤ ks.length := findChild_le ks x
omega
On an internal bundled node, the child selected by findChild has a
concrete getElem? witness.
theorem findChild_exists
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) (hchildren : cs ≠ []) (x : Nat) :
∃ child, cs[findChild ks x]? = some child :=
getElem?_exists_of_lt (h.findChild_lt hchildren x)The missing-current-child fallback is unreachable in an internal bundled node.
theorem findChild_none_absurd
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) (hchildren : cs ≠ [])
{x : Nat} (hnone : cs[findChild ks x]? = none) :
False := by
obtain ⟨child, hchild⟩ := h.findChild_exists hchildren x
rw [hnone] at hchild
simp at hchildAt a positive child-search index, the missing-left-sibling fallback is unreachable in an internal bundled node.
theorem findChild_leftSibling_none_absurd
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) (hchildren : cs ≠ [])
{x : Nat} (hpos : 0 < findChild ks x)
(hnone : cs[findChild ks x - 1]? = none) :
False := by
have hcurrentLt := h.findChild_lt hchildren x
obtain ⟨left, hleft⟩ :=
getElem?_pred_exists hpos (Nat.le_of_lt hcurrentLt)
rw [hnone] at hleft
simp at hleftEvery nonempty child list in a bundled node has at least two entries when the minimum degree is at least two. This is the root/non-root common form needed by the right-sibling branch at child index zero.
theorem two_le_children_of_not_empty
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) (ht : 2 ≤ t)
(hchildren : cs ≠ []) :
2 ≤ cs.length := by
have hoccupancy := h.occupancy
unfold Occupancy at hoccupancy
cases isRoot with
| false =>
simp only [Bool.false_eq_true, ↓reduceIte] at hoccupancy
rcases hoccupancy.2.2.1 with hempty | hbounds
· exact absurd (List.isEmpty_iff.mp hempty) hchildren
· omega
| true =>
simp only [↓reduceIte] at hoccupancy
rcases hoccupancy.2.2.1 with hempty | hbounds
· exact absurd (List.isEmpty_iff.mp hempty) hchildren
· exact hbounds.1The right sibling of child zero exists in every internal bundled node.
theorem rightSibling_zero_exists
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) (ht : 2 ≤ t)
(hchildren : cs ≠ []) :
∃ right, cs[1]? = some right :=
getElem?_exists_of_lt (h.two_le_children_of_not_empty ht hchildren)
Whenever child i + 1 exists, the separator immediately before it is
present in the parent key list.
theorem separator_before_rightSibling_exists
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) (hchildren : cs ≠ [])
{i : Nat} {right : BTree} (hright : cs[i + 1]? = some right) :
∃ sep, ks[i]? = some sep := by
have hrightIndex : i + 1 < cs.length :=
(List.getElem?_eq_some_iff.mp hright).1
have hlength : cs.length = ks.length + 1 :=
h.children_rel.resolve_left hchildren
exact getElem?_exists_of_lt (by omega)The child to the left of a present separator key exists in every internal bundled node.
theorem leftChild_exists_of_key
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
{ki sep : Nat} (h : NodeWF t isRoot (node ks cs)) (hchildren : cs ≠ [])
(hkey : ks[ki]? = some sep) :
∃ child, cs[ki]? = some child := by
have hki : ki < ks.length := (List.getElem?_eq_some_iff.mp hkey).1
have hlength : cs.length = ks.length + 1 :=
h.children_rel.resolve_left hchildren
exact getElem?_exists_of_lt (by omega)The child to the right of a present separator key exists in every internal bundled node; the right child is at separator index plus one.
theorem rightChild_exists_of_key
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
{ki sep : Nat} (h : NodeWF t isRoot (node ks cs)) (hchildren : cs ≠ [])
(hkey : ks[ki]? = some sep) :
∃ child, cs[ki + 1]? = some child := by
have hki : ki < ks.length := (List.getElem?_eq_some_iff.mp hkey).1
have hlength : cs.length = ks.length + 1 :=
h.children_rel.resolve_left hchildren
exact getElem?_exists_of_lt (by omega)Any two children of a bundled node have the same height.
theorem siblings_height
{t : Nat} {isRoot : Bool} {ks : List Nat} {cs : List BTree}
(h : NodeWF t isRoot (node ks cs)) {left right : BTree}
(hleft : left ∈ cs) (hright : right ∈ cs) :
heightOf left = heightOf right :=
(sameDepth_iff.mp h.sameDepth).2 left hleft right hrightProject the complete local facts for two children adjacent to a present separator: both child packets, equal height, and the two separator key bounds.
theorem adjacent_children
{t j sep : Nat} {isRoot : Bool}
{ks : List Nat} {cs : List BTree} {left right : BTree}
(h : NodeWF t isRoot (node ks cs))
(hsep : ks[j]? = some sep)
(hleft : cs[j]? = some left)
(hright : cs[j + 1]? = some right) :
NodeWF t false left ∧ NodeWF t false right ∧
heightOf left = heightOf right ∧
(∀ k ∈ keysOf left, k ≤ sep) ∧
(∀ k ∈ keysOf right, sep ≤ k) := by
obtain ⟨hjLeft, hleftGetElem⟩ :=
List.getElem?_eq_some_iff.mp hleft
obtain ⟨hjRight, hrightGetElem⟩ :=
List.getElem?_eq_some_iff.mp hright
have hleftGet : cs.get ⟨j, hjLeft⟩ = left := by
rw [List.get_eq_getElem]
exact hleftGetElem
have hrightGet : cs.get ⟨j + 1, hjRight⟩ = right := by
rw [List.get_eq_getElem]
exact hrightGetElem
have hleftMem : left ∈ cs :=
List.mem_iff_getElem?.mpr ⟨j, hleft⟩
have hrightMem : right ∈ cs :=
List.mem_iff_getElem?.mpr ⟨j + 1, hright⟩
have hbounds := h.childBounded
unfold ChildBounded at hbounds
have hleftBounds := hbounds.2.1 j hjLeft
rw [hleftGet, hsep] at hleftBounds
have hrightBounds := hbounds.2.1 (j + 1) hjRight
rw [hrightGet] at hrightBounds
have hrightLower : ∀ k ∈ keysOf right, sep ≤ k := by
rcases hrightBounds.1 with hzero | hlower
· omega
· rw [show j + 1 - 1 = j by omega, hsep] at hlower
exact hlower
exact
⟨h.child hleftMem, h.child hrightMem,
h.siblings_height hleftMem hrightMem,
hleftBounds.2, hrightLower⟩end NodeWF
A non-ready occupied non-root node has exactly the minimum t - 1 keys.
theorem numKeys_eq_t_sub_one_of_not_ready
{t : Nat} {tr : BTree} (hoccupancy : Occupancy t false tr)
(hnotReady : ¬ t ≤ numKeys tr) :
numKeys tr = t - 1 := by
rcases tr with ⟨ks, cs⟩
have hlower : t - 1 ≤ ks.length := (occupancy_false_dest hoccupancy).1
change ¬ t ≤ ks.length at hnotReady
change ks.length = t - 1
omeganamespace KeysSubsetEvery tree's represented keys are a subset of themselves.
theorem refl (tr : BTree) : KeysSubset tr tr := by
intro k hk
exact hkKey containment composes through an intermediate tree.
theorem trans {after middle before : BTree}
(h₁ : KeysSubset after middle) (h₂ : KeysSubset middle before) :
KeysSubset after before := by
intro k hk
exact h₂ k (h₁ k hk)end KeysSubsetend BTreeend Chapter18end CLRS