String matching with finite automata
DefinitionTransition table of the string-matching automaton#✓fully proved
Let \(P\) be a pattern of length \(m\) over the alphabet \(\{0, \dots, A-1\}\) and write \(P_k\) for its prefix of length \(k\). This timed computation returns a table of \((m+1)A\) entries in \(\{0, \dots, m\}\), stored row by row: for each state \(q \le m\) and symbol \(a < A\), the entry at index \(qA + a\) is found by trying \(k = \min(m, q+1)\) and then each smaller value in turn, and is the first \(k\) for which \(P_k\) is a suffix of the word \(P_q a\) (the candidate \(k = 0\) is always accepted). The cost counts one unit for each candidate \(k\) tried, one for each symbol comparison executed when comparing \(P_k\) with the last \(k\) symbols of \(P_q a\) (left to right, stopping at the first mismatch), one for each move to a smaller candidate, and one for each table entry written.
def computeTransition {A : Nat} (pattern : List (Fin A)) :
TimeM Nat (Vector (Fin (pattern.length + 1)) ((pattern.length + 1) * A)) :=
Vector.ofFnM fun i => do
let q ← typedTransitionCell pattern (rowOfIndex i) (columnOfIndex i)
TimeM.tick 1
pure q
@[no_expose]
private def typedTransitionCell {A : Nat} (pattern : List (Fin A))
(q : Fin (pattern.length + 1)) (a : Fin A) : TimeM Nat (Fin (pattern.length + 1)) :=
let r := transitionCell pattern q.val a
{ ret := ⟨r.ret, Nat.lt_succ_of_le
(transitionCell_spec pattern q.val (Nat.le_of_lt_succ q.isLt) a).1.1⟩
time := r.time }
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:232
@[no_expose]
private def rowOfIndex {A n : Nat} (i : Fin (n * A)) : Fin n :=
⟨i.val / A, (Nat.div_lt_iff_lt_mul (positiveAlphabet i)).mpr i.isLt⟩
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:203
@[no_expose]
private def columnOfIndex {A n : Nat} (i : Fin (n * A)) : Fin A :=
⟨i.val % A, Nat.mod_lt _ (positiveAlphabet i)⟩
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:207
Sources
- CLRS fourth edition section 32.3, decreasing-candidate row-major construction.
TheoremCorrectness of the string-matching transition table#✓fully proved
Let \(P\) be a pattern of length \(m\) over the alphabet \(\{0, \dots, A-1\}\) and write \(P_k\) for its prefix of length \(k\). For every state \(q \le m\) and symbol \(a < A\), the entry at index \(qA + a\) of the transition table of \(P\) is the greatest \(k \le m\) such that \(P_k\) is a suffix of the word \(P_q a\).
theorem computeTransition_spec {A : Nat} (pattern : List (Fin A))
(q : Fin (pattern.length + 1)) (a : Fin A) :
IsGreatest {k : Nat | k ≤ pattern.length ∧ pattern.take k <:+ pattern.take q.val ++ [a]}
((computeTransition pattern).ret.get ⟨q.val * A + a.val, by
calc
q.val * A + a.val < q.val * A + A := Nat.add_lt_add_left a.isLt _
_ = (q.val + 1) * A := (Nat.succ_mul q.val A).symm
_ ≤ (pattern.length + 1) * A :=
Nat.mul_le_mul_right A (Nat.succ_le_of_lt q.isLt)⟩).val
def computeTransition {A : Nat} (pattern : List (Fin A)) :
TimeM Nat (Vector (Fin (pattern.length + 1)) ((pattern.length + 1) * A)) :=
Vector.ofFnM fun i => do
let q ← typedTransitionCell pattern (rowOfIndex i) (columnOfIndex i)
TimeM.tick 1
pure q
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:242
Uses 1 · Used by 2
TheoremCost bound for the string-matching transition table#✓fully proved
For every pattern of length \(m\) over the alphabet \(\{0, \dots, A-1\}\), the cost of computing its transition table is at most \(2A(m+1)^3\).
The cost counts executed candidates, symbol comparisons, failed decrements and table writes.
theorem computeTransition_time_le {A : Nat} (pattern : List (Fin A)) :
(computeTransition pattern).time ≤ 2 * A * (pattern.length + 1) ^ 3
def computeTransition {A : Nat} (pattern : List (Fin A)) :
TimeM Nat (Vector (Fin (pattern.length + 1)) ((pattern.length + 1) * A)) :=
Vector.ofFnM fun i => do
let q ← typedTransitionCell pattern (rowOfIndex i) (columnOfIndex i)
TimeM.tick 1
pure q
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:242
Uses 1
DefinitionString-matching automaton#✓fully proved
For a pattern \(P\) of length \(m\) over the alphabet \(\{0, \dots, A-1\}\), the string-matching automaton of \(P\) is the deterministic finite automaton with states \(\{0, \dots, m\}\), input symbols \(\{0, \dots, A-1\}\), start state \(0\), accepting set \(\{m\}\), and transition from state \(q\) on symbol \(a\) given by the entry at index \(qA + a\) of the transition table of \(P\).
def patternAutomaton {A : Nat} (pattern : List (Fin A)) :
Cslib.Automata.DA.FinAcc (Fin (pattern.length + 1)) (Fin A) :=
let table := (computeTransition pattern).ret
{ start := 0
tr := fun q a => table.get (flatIndex q a)
accept := {q | q.val = pattern.length} }
def computeTransition {A : Nat} (pattern : List (Fin A)) :
TimeM Nat (Vector (Fin (pattern.length + 1)) ((pattern.length + 1) * A)) :=
Vector.ofFnM fun i => do
let q ← typedTransitionCell pattern (rowOfIndex i) (columnOfIndex i)
TimeM.tick 1
pure q
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:242
@[no_expose]
private def flatIndex {A n : Nat} (q : Fin n) (a : Fin A) : Fin (n * A) :=
⟨q.val * A + a.val, by
calc
q.val * A + a.val < q.val * A + A := Nat.add_lt_add_left a.isLt _
_ = (q.val + 1) * A := (Nat.succ_mul q.val A).symm
_ ≤ n * A := Nat.mul_le_mul_right A (Nat.succ_le_of_lt q.isLt)⟩
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:211
Uses 1 · Used by 2
TheoremThe string-matching automaton tracks the longest matched prefix#✓fully proved
Let \(P\) be a pattern of length \(m\) over the alphabet \(\{0, \dots, A-1\}\) and write \(P_k\) for its prefix of length \(k\). For every word \(w\) over this alphabet, the state that the string-matching automaton of \(P\) reaches from its start state after reading \(w\) is the greatest \(k \le m\) such that \(P_k\) is a suffix of \(w\).
theorem patternAutomaton_state {A : Nat} (pattern word : List (Fin A)) :
IsGreatest {k : Nat | k ≤ pattern.length ∧ pattern.take k <:+ word}
((patternAutomaton pattern).mtr (patternAutomaton pattern).start word).val
def patternAutomaton {A : Nat} (pattern : List (Fin A)) :
Cslib.Automata.DA.FinAcc (Fin (pattern.length + 1)) (Fin A) :=
let table := (computeTransition pattern).ret
{ start := 0
tr := fun q a => table.get (flatIndex q a)
accept := {q | q.val = pattern.length} }
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:335
Uses 2 · Used by 1
DefinitionFinite-automaton string matcher#✓fully proved
Let \(P\) be a pattern of length \(m\) and \(T\) a text over the alphabet \(\{0, \dots, A-1\}\). This timed computation first computes the transition table of \(P\), then runs through \(T\) starting in state \(0\): on reading the \(i\)-th symbol \(a\) of \(T\) in state \(q\) it moves to the state given by the table entry at index \(qA + a\), and if that state is \(m\) it reports the shift \(i - m\). If \(m = 0\), the shift \(0\) is also reported before the scan. It returns the reported shifts in the order found. The cost is that of computing the table, plus two units for each symbol of \(T\) (a table lookup and an acceptance test), plus one unit for each reported shift.
The table is computed once, before the single pass over the text, and the scan does not reset after a match, so overlapping occurrences are reported.
def finiteAutomatonMatches {A : Nat} (pattern text : List (Fin A)) : TimeM Nat (List Nat) := do
let table ← computeTransition pattern
if pattern.length = 0 then do
TimeM.tick 1
scan table 0 0 (pure [0]) text
else scan table 0 0 (pure []) text
def computeTransition {A : Nat} (pattern : List (Fin A)) :
TimeM Nat (Vector (Fin (pattern.length + 1)) ((pattern.length + 1) * A)) :=
Vector.ofFnM fun i => do
let q ← typedTransitionCell pattern (rowOfIndex i) (columnOfIndex i)
TimeM.tick 1
pure q
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:242
@[no_expose]
private def scan {A m : Nat} (table : Vector (Fin (m + 1)) ((m + 1) * A)) :
Fin (m + 1) → Nat → TimeM Nat (List Nat) → List (Fin A) → TimeM Nat (List Nat)
| _, _, out, [] => { ret := out.ret.reverse, time := out.time }
| q, position, out, a :: rest =>
let step := scanStep table q position out a
scan table step.ret.1 (position + 1) { ret := step.ret.2, time := step.time } rest
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:380
Uses 1 · Used by 2
TheoremExact cost of the finite-automaton matcher#✓fully proved
For every pattern \(P\) and text \(T\) over the alphabet \(\{0, \dots, A-1\}\), where \(T\) has length \(n\), the cost of the finite-automaton matcher on \(P\) and \(T\) equals the cost of computing the transition table of \(P\), plus \(2n\), plus the number of shifts the matcher returns.
theorem finiteAutomatonMatches_time {A : Nat} (pattern text : List (Fin A)) :
(finiteAutomatonMatches pattern text).time =
(computeTransition pattern).time + 2 * text.length +
(finiteAutomatonMatches pattern text).ret.length
def finiteAutomatonMatches {A : Nat} (pattern text : List (Fin A)) : TimeM Nat (List Nat) := do
let table ← computeTransition pattern
if pattern.length = 0 then do
TimeM.tick 1
scan table 0 0 (pure [0]) text
else scan table 0 0 (pure []) text
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:423
def computeTransition {A : Nat} (pattern : List (Fin A)) :
TimeM Nat (Vector (Fin (pattern.length + 1)) ((pattern.length + 1) * A)) :=
Vector.ofFnM fun i => do
let q ← typedTransitionCell pattern (rowOfIndex i) (columnOfIndex i)
TimeM.tick 1
pure q
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:242
Uses 2
TheoremCorrectness of the finite-automaton matcher#✓fully proved
For every pattern \(P\) and text \(T\) over the alphabet \(\{0, \dots, A-1\}\), the list of shifts returned by the finite-automaton matcher equals the list returned by the naive string matcher.
So it is the complete increasing list of zero-based shifts at which \(P\) occurs in \(T\), including overlapping occurrences and, for the empty pattern, every boundary.
theorem finiteAutomatonMatches_ret {A : Nat} (pattern text : List (Fin A)) :
(finiteAutomatonMatches pattern text).ret = (TimeM.naiveMatches pattern text).ret
def finiteAutomatonMatches {A : Nat} (pattern text : List (Fin A)) : TimeM Nat (List Nat) := do
let table ← computeTransition pattern
if pattern.length = 0 then do
TimeM.tick 1
scan table 0 0 (pure [0]) text
else scan table 0 0 (pure []) text
CSLibExt/Algorithms/Lean/StringMatching/FiniteAutomaton.lean:423
public def naiveMatches [BEq α] (pattern text : List α) : TimeM Nat (List Nat) :=
if pattern.length ≤ text.length then
timedScan pattern (text.tails.take (text.length - pattern.length + 1)) 0
else
pure []
CSLibExt/Algorithms/Lean/StringMatching/Naive.lean:66
Uses 9
Loading dependency explorer…
The dependency explorer needs JavaScript and its graph data.