Executed finite secondary perfect-hash construction
Every candidate runs a collision check, initializes its secondary slot array,
and places its local key indices. The returned ledger counts hash evaluations,
equality comparisons, slot initialization, and indexed writes. Supplied finite
traces stop on success; exhaustion executes a deterministic injective fallback.
Thus the actual attempts equal the existing failedTrials + 1 statistic.
Hashes act on local indices Fin n. Hash-family sampling and representation
costs are outside this indexed-operation model. The finite expectation concerns
uniform full assignments, and is not a theorem about an unbounded retry process.
namespace CLRS.Chapter11.PerfectConstructionHash equality tests, charged for two hash evaluations and one comparison.
def differsFrom (a : α → β) [DecidableEq β] (x : α) : List α → Bool × Nat
| [] => (true, 0)
| y :: ys =>
if a x = a y then (false, 3)
else let rest := differsFrom a x ys; (rest.1, rest.2 + 3)theorem differsFrom_correct (a : α → β) [DecidableEq β] (x : α) (xs : List α) :
(differsFrom a x xs).1 = true ↔ ∀ y ∈ xs, a x ≠ a y := by
induction xs with
| nil => simp [differsFrom]
| cons y ys ih => by_cases h : a x = a y <;> simp [differsFrom, h, ih]theorem differsFrom_work_le (a : α → β) [DecidableEq β] (x : α) (xs : List α) :
(differsFrom a x xs).2 ≤ 3 * xs.length := by
induction xs with
| nil => simp [differsFrom]
| cons y ys ih => simp only [differsFrom]; split <;> simp_all; omegaCheck all distinct input positions, stopping at the first duplicate hash.
def checkDistinct (a : α → β) [DecidableEq β] : List α → Bool × Nat
| [] => (true, 0)
| x :: xs =>
let head := differsFrom a x xs
if head.1 then
let tail := checkDistinct a xs
(tail.1, head.2 + tail.2)
else (false, head.2)theorem checkDistinct_correct (a : α → β) [DecidableEq β] (xs : List α) :
(checkDistinct a xs).1 = true ↔ (xs.map a).Nodup := by
induction xs with
| nil => simp [checkDistinct]
| cons x xs ih =>
simp only [checkDistinct]
split
next h =>
have hh := (differsFrom_correct a x xs).mp h
simp only [List.map_cons, List.nodup_cons, List.mem_map]
constructor
· intro ht
exact ⟨by rintro ⟨y, hy, he⟩; exact hh y hy he.symm, ih.mp ht⟩
· intro ht; exact ih.mpr ht.2
next h =>
simp only [Bool.false_eq_true, List.map_cons, List.nodup_cons, false_iff]
intro hn
apply h
apply (differsFrom_correct a x xs).mpr
intro y hy he
exact hn.1 (List.mem_map.mpr ⟨y, hy, he.symm⟩)theorem checkDistinct_work_le (a : α → β) [DecidableEq β] (xs : List α) :
(checkDistinct a xs).2 ≤ 3 * xs.length ^ 2 := by
induction xs with
| nil => simp [checkDistinct]
| cons x xs ih =>
have hh := differsFrom_work_le a x xs
simp only [checkDistinct]
split <;> simp only [List.length_cons] <;> nlinarithabbrev Hash (n : Nat) := Fin n → Fin (n ^ 2)
theorem check_hash_correct {n : Nat} (a : Hash n) :
(checkDistinct a (List.finRange n)).1 = true ↔ collisionFree a := by
rw [checkDistinct_correct, List.nodup_map_iff_inj_on (List.nodup_finRange n)]
simp [collisionFree]Initialize actual empty secondary slots, counting each array push.
def emptySlots (α : Type*) : Nat → Array (Option α) × Nat
| 0 => (#[], 0)
| m + 1 => let prev := emptySlots α m; (prev.1.push none, prev.2 + 1)@[simp] theorem emptySlots_array (α : Type*) (m : Nat) :
(emptySlots α m).1 = Array.replicate m none := by
induction m with
| zero => simp [emptySlots]
| succ m ih => simp [emptySlots, ih, Array.replicate_succ]@[simp] theorem emptySlots_work (α : Type*) (m : Nat) : (emptySlots α m).2 = m := by
induction m with
| zero => rfl
| succ m ih => simp [emptySlots, ih]Place each key in its selected slot. A step charges a hash evaluation and an indexed write; initialization and collision checks are counted separately.
def place {n : Nat} (a : Hash n) : List (Fin n) → Array (Option (Fin n)) →
Array (Option (Fin n)) × Nat
| [], out => (out, 0)
| i :: is, out =>
let rest := place a is out
(rest.1.setIfInBounds (a i).val (some i), rest.2 + 2)@[simp] theorem place_size {n : Nat} (a : Hash n) (xs : List (Fin n)) (out : Array (Option (Fin n))) :
(place a xs out).1.size = out.size := by
induction xs with
| nil => rfl
| cons i xs ih => simp [place, ih]@[simp] theorem place_work {n : Nat} (a : Hash n) (xs : List (Fin n)) (out : Array (Option (Fin n))) :
(place a xs out).2 = 2 * xs.length := by
induction xs with
| nil => simp [place]
| cons i xs ih => simp [place, ih]; omegaEach slot contains the first input key assigned there, or its initial value.
theorem place_get {n : Nat} (a : Hash n) (xs : List (Fin n))
(out : Array (Option (Fin n))) (s : Nat) (hs : s < out.size) :
(place a xs out).1[s]'(by simpa using hs) =
(xs.find? (fun i => (a i).val == s)).orElse (fun _ => out[s]) := by
induction xs with
| nil => simp [place]
| cons i xs ih =>
simp only [place]
rw [Array.getElem_setIfInBounds (by simpa using hs)]
by_cases he : (a i).val = s <;> simp [he, List.find?, ih, Bool.beq_eq_decide_eq]structure Attempt (n : Nat) where
hash : Hash n
slots : Array (Option (Fin n))
success : Bool
work : NatAn actual collision check followed by secondary-array placement.
def attempt {n : Nat} (a : Hash n) : Attempt n :=
let checked := checkDistinct a (List.finRange n)
let initial := emptySlots (Fin n) (n ^ 2)
let filled := place a (List.finRange n) initial.1
⟨a, filled.1, checked.1, checked.2 + initial.2 + filled.2⟩@[simp] theorem attempt_hash {n : Nat} (a : Hash n) : (attempt a).hash = a := rfl@[simp] theorem attempt_size {n : Nat} (a : Hash n) : (attempt a).slots.size = n ^ 2 := by
simp [attempt]@[simp] theorem attempt_success {n : Nat} (a : Hash n) :
(attempt a).success = true ↔ collisionFree a := check_hash_correct a
theorem attempt_work_le {n : Nat} (a : Hash n) : (attempt a).work ≤ 6 * n ^ 2 := by
have h := checkDistinct_work_le a (List.finRange n)
have hn : n ≤ n ^ 2 := by nlinarith
simpa only [attempt, emptySlots_work, place_work, List.length_finRange] using
(show (checkDistinct a (List.finRange n)).2 + n ^ 2 + 2 * n ≤ 6 * n ^ 2 by
simp only [List.length_finRange] at h
nlinarith)
theorem attempt_get {n : Nat} (a : Hash n) (s : Nat) (hs : s < n ^ 2) :
(attempt a).slots[s]'(by simpa using hs) =
(List.finRange n).find? (fun i => (a i).val == s) := by
have h := place_get a (List.finRange n) (emptySlots (Fin n) (n ^ 2)).1 s (by simpa using hs)
simpa [attempt] using h
theorem attempt_stores {n : Nat} (a : Hash n) (ha : collisionFree a) (i : Fin n) :
(attempt a).slots[(a i).val]'(by simp) = some i := by
rw [attempt_get a _ (a i).isLt]
cases h : (List.finRange n).find? (fun j => (a j).val == (a i).val) with
| none =>
have hh := List.find?_eq_none.mp h i (by simp)
simp at hh
| some j =>
have hh := List.find?_some h
have he : a j = a i := Fin.ext (by simpa using hh)
rw [ha j i he]The terminal assignment is explicitly injective, including the empty domain.
def fallback (n : Nat) : Hash n := fun i =>
⟨i.val, lt_of_lt_of_le i.isLt (by nlinarith : n ≤ n ^ 2)⟩theorem fallback_injective (n : Nat) : collisionFree (fallback n) := by
intro i j hij
exact Fin.ext (congrArg (fun x : Fin (n ^ 2) => x.val) hij)structure Build (n : Nat) where
selected : Attempt n
attempts : Nat
work : NatTry supplied candidates in order; after their exhaustion execute one explicit injective fallback. Every returned table therefore succeeds.
def build {n : Nat} : List (Hash n) → Build n
| [] => let final := attempt (fallback n); ⟨final, 1, final.work⟩
| a :: as =>
let trial := attempt a
if trial.success then ⟨trial, 1, trial.work⟩
else let rest := build as; ⟨rest.selected, rest.attempts + 1, trial.work + rest.work⟩theorem build_success {n : Nat} (as : List (Hash n)) : (build as).selected.success = true := by
induction as with
| nil => simpa [build] using (attempt_success (fallback n)).mpr (fallback_injective n)
| cons a as ih =>
simp only [build]
split
· assumption
· exact ihtheorem build_work_le {n : Nat} (as : List (Hash n)) :
(build as).work ≤ 6 * n ^ 2 * (build as).attempts := by
induction as with
| nil => simpa [build] using attempt_work_le (fallback n)
| cons a as ih =>
have h := attempt_work_le a
simp only [build]
split <;> simp only <;> nlinarithdef failedPrefix {n : Nat} : List (Hash n) → Nat
| [] => 0
| a :: as => if (attempt a).success then 0 else failedPrefix as + 1theorem build_attempts {n : Nat} (as : List (Hash n)) :
(build as).attempts = failedPrefix as + 1 := by
induction as with
| nil => simp [build, failedPrefix]
| cons a as ih => simp only [build, failedPrefix]; split <;> simp_all
lemma failedTrials_cons {n t : Nat} (A : Fin (t + 1) → Hash n) :
failedTrials A = if collisionFree (A 0) then 0 else
failedTrials (fun j : Fin t => A j.succ) + 1 := by
classical
rw [failedTrials_eq_sum, Fin.sum_univ_succ]
have htail (k : Fin t) :
(∀ j : Fin (k.succ.val + 1),
¬ collisionFree (A (Fin.castLE (Nat.succ_le_of_lt k.succ.isLt) j))) ↔
(¬ collisionFree (A 0) ∧ ∀ j : Fin (k.val + 1),
¬ collisionFree (A (Fin.castLE (Nat.succ_le_of_lt k.isLt) j).succ)) := by
rw [Fin.forall_fin_succ]
rfl
have hzero :
(∀ j : Fin ((0 : Fin (t + 1)).val + 1),
¬ collisionFree (A (Fin.castLE (Nat.succ_le_of_lt (0 : Fin (t + 1)).isLt) j))) ↔
¬ collisionFree (A 0) := by
constructor
· intro hh
exact hh 0
· intro hh j
have hj : Fin.castLE (Nat.succ_le_of_lt (0 : Fin (t + 1)).isLt) j = 0 := by
apply Fin.ext
have h := j.isLt
change j.val < 1 at h
change j.val = 0
omega
rw [hj]
exact hh
simp_rw [htail, hzero]
by_cases h : collisionFree (A 0)
· have hh : collisionFree (A 0) ↔ True := iff_true_intro h
simp only [hh, not_true_eq_false, false_and, ite_false, Finset.sum_const_zero,
zero_add, ite_true]
· simp only [h, not_false_eq_true, true_and, ite_true, ite_false]
rw [failedTrials_eq_sum]
omega
theorem failedPrefix_ofFn {n t : Nat} (A : Fin t → Hash n) :
failedPrefix (List.ofFn A) = failedTrials A := by
classical
induction t with
| zero => simp [List.ofFn_zero, failedPrefix, failedTrials]
| succ t ih =>
rw [List.ofFn_succ, failedPrefix, failedTrials_cons]
simp only [attempt_success, ih]def buildTrace {n : Nat} : {t : Nat} → (Fin t → Hash n) → Build n
| 0, _ => build []
| _ + 1, A =>
let current := attempt (A 0)
if current.success then ⟨current, 1, current.work⟩
else
let rest := buildTrace (fun j => A j.succ)
⟨rest.selected, rest.attempts + 1, current.work + rest.work⟩theorem buildTrace_eq {n t : Nat} (A : Fin t → Hash n) :
buildTrace A = build (List.ofFn A) := by
induction t with
| zero => simp [buildTrace]
| succ t ih => simp [buildTrace, List.ofFn_succ, build, ih]The legacy finite trial random variable is the exact attempt count of finite sampling followed by the terminal injective fallback.
theorem buildTrace_attempts {n t : Nat} (A : Fin t → Hash n) :
(buildTrace A).attempts = trialsUntilCollisionFree A := by
rw [buildTrace_eq, build_attempts, failedPrefix_ofFn, trialsUntilCollisionFree]theorem buildTrace_work_le {n t : Nat} (A : Fin t → Hash n) :
(buildTrace A).work ≤ 6 * n ^ 2 * trialsUntilCollisionFree A := by
simpa [← buildTrace_attempts A, buildTrace_eq] using build_work_le (List.ofFn A)open CLRS.ProbabilityExpected measured secondary work over the stated finite SUHA trace space.
theorem expected_buildTrace_work_le {n t : Nat} (hn : 2 ≤ n) :
fintypeExpect (fun A : Fin t → Hash n => ((buildTrace A).work : ℝ)) ≤
12 * (n : ℝ) ^ 2 := by
classical
calc
fintypeExpect (fun A : Fin t → Hash n => ((buildTrace A).work : ℝ)) ≤
fintypeExpect (fun A : Fin t → Hash n =>
6 * (n : ℝ) ^ 2 * (trialsUntilCollisionFree A : ℝ)) := by
apply fintypeExpect_mono
intro A
exact_mod_cast buildTrace_work_le A
_ = (6 * (n : ℝ) ^ 2) *
fintypeExpect (fun A : Fin t → Hash n => (trialsUntilCollisionFree A : ℝ)) :=
fintypeExpect_const_mul _ _
_ ≤ (6 * (n : ℝ) ^ 2) * 2 :=
mul_le_mul_of_nonneg_left (perfectHash_expected_trials_le_two hn) (by positivity)
_ = _ := by ringEvery selected result is a real executed secondary attempt.
theorem build_selected_eq {n : Nat} (as : List (Hash n)) :
(build as).selected = attempt (build as).selected.hash := by
induction as with
| nil => rfl
| cons a as ih => simp only [build]; split <;> first | rfl | exact ih
theorem build_hash_injective {n : Nat} (as : List (Hash n)) :
collisionFree (build as).selected.hash := by
have h := build_success as
rw [build_selected_eq] at h
exact attempt_success _ |>.mp h
theorem attempt_only {n : Nat} (a : Hash n) (s : Nat) (i : Fin n)
(h : (attempt a).slots[s]?.join = some i) : (a i).val = s := by
by_cases hs : s < n ^ 2
· have hsize : s < (attempt a).slots.size := by simpa using hs
rw [Array.getElem?_eq_getElem hsize, Option.join_some, attempt_get a s hs] at h
have hh := List.find?_some h
simpa using hh
· have hsize : ¬ s < (attempt a).slots.size := by simpa using hs
simp [Array.getElem?_eq_none (Nat.le_of_not_gt hsize)] at hPackage the constructed array as a one-bucket perfect table. All key slots come from the returned placement execution, rather than from a specification search.
def tableOfBuild {n : Nat} (as : List (Hash n)) : PerfectHashTable (Fin n) 1 where
keys := Finset.univ
prim := fun _ => 0
sec := fun _ i => ((build as).selected.hash i).val
table := fun _ s => (build as).selected.slots[s]?.join
sec_inj := by
intro j x y hx hy hpx hpy hs
exact build_hash_injective as x y (Fin.ext hs)
table_stores_keys := by
intro i hi
rw [build_selected_eq]
have hs := attempt_stores (build as).selected.hash (build_hash_injective as) i
simp only [attempt_hash]
rw [Array.getElem?_eq_getElem (by simp), Option.join_some]
exact hs
table_only_keys := by
intro j s i hi
refine ⟨by simp, Subsingleton.elim _ _, ?_⟩
rw [build_selected_eq] at hi
exact attempt_only _ s i hiThe successful finite builder has a verified membership-query interface.
theorem tableOfBuild_search {n : Nat} (as : List (Hash n)) (i : Fin n) :
perfectSearch (tableOfBuild as) i := by
rw [perfectSearch_iff_mem]
simp [tableOfBuild]
theorem build_work_le_small {n : Nat} (hn : n ≤ 1) (as : List (Hash n)) :
(build as).work ≤ 6 * n ^ 2 := by
cases as with
| nil => simpa [build] using attempt_work_le (fallback n)
| cons a as =>
have hc : collisionFree a := by
intro i j hij
apply Fin.ext
have hi := i.isLt
have hj := j.isLt
omega
have hs := (attempt_success a).mpr hc
simpa only [build, hs, Bool.true_eq, ↓reduceIte] using attempt_work_le aEmpty and singleton buckets are handled directly; the SUHA retry bound is needed only for buckets with at least two keys.
theorem expected_buildTrace_work_le_all (n t : Nat) :
fintypeExpect (fun A : Fin t → Hash n => ((buildTrace A).work : ℝ)) ≤
12 * (n : ℝ) ^ 2 := by
classical
by_cases hn : 2 ≤ n
· exact expected_buildTrace_work_le hn
haveI : Nonempty (Fin t → Hash n) := ⟨fun _ => fallback n⟩
calc
fintypeExpect (fun A : Fin t → Hash n => ((buildTrace A).work : ℝ)) ≤
fintypeExpect (fun _ : Fin t → Hash n => 6 * (n : ℝ) ^ 2) := by
apply fintypeExpect_mono
intro A
rw [buildTrace_eq]
exact_mod_cast build_work_le_small (by omega : n ≤ 1) (List.ofFn A)
_ = 6 * (n : ℝ) ^ 2 := fintypeExpect_const Fintype.card_ne_zero _
_ ≤ _ := by nlinarith [sq_nonneg (n : ℝ)]end CLRS.Chapter11.PerfectConstruction