Algorithm Search⌘K

Proofs: Set Horspool algorithm

These are the human proofs of Set Horspool algorithm, at full length.

Set Horspool outputs exactly the occurrences of the patterns full

This proof shows: correctness, termination.

Claim. For every text T[0…n−1]T[0 \ldots n-1] and patterns P0,…,Pk−1P_0, \ldots, P_{k-1}, SET-HORSPOOL stops and outputs each pair (s,j)(s, j) with 0≤s≤n−∣Pj∣0 \le s \le n - |P_j| and T[s…s+∣Pj∣−1]=PjT[s \ldots s + |P_j| - 1] = P_j exactly once, and no other pair.

Notation. For a string xx, xRx^R is its reverse. In the trie of line L3, each node vv spells a string str(v)\mathrm{str}(v) (the labels read from the root), and out[v]={j:Pj≠ε, PjR=str(v)}\mathrm{out}[v] = \{ j : P_j \ne \varepsilon,\ P_j^R = \mathrm{str}(v) \}. A pattern PjP_j ends at ee if it occurs at e−∣Pj∣+1e - |P_j| + 1. For a position ee, let Ue=T[e] T[e−1]⋯T[0]U_e = T[e]\, T[e-1] \cdots T[0] be the text read backward from ee.

Lemma 1 (the trie). A string uu is str(v)\mathrm{str}(v) for some node vv if and only if uu is a prefix of PjRP_j^R for some nonempty PjP_j. Each node has at most one child per character.

Proof. Line L3 inserts each PjRP_j^R from the root, creating a child only when none exists for the next character. So the nodes are exactly the prefixes of the inserted strings, and children are unique per character. □\square

Lemma 2 (one reading). Fix pos\mathrm{pos} with 0≤pos≤n−10 \le \mathrm{pos} \le n - 1. Lines L9 to L12 output exactly the pairs (pos−∣Pj∣+1,j)(\mathrm{pos} - |P_j| + 1, j) for the nonempty PjP_j that end at pos\mathrm{pos}, each once.

Proof. Let gg be the length of the longest prefix of UposU_{\mathrm{pos}} that is a node. By Lemma 1 and induction on the iterations of line L10, after tt iterations vv is the node of T[pos]⋯T[pos−t+1]T[\mathrm{pos}] \cdots T[\mathrm{pos} - t + 1] and i=pos−ti = \mathrm{pos} - t; the loop continues as long as the next prefix is a node and i≥0i \ge 0, so it makes exactly gg iterations. The node reached after tt iterations has the string T[pos−t+1…pos]RT[\mathrm{pos} - t + 1 \ldots \mathrm{pos}]^R, so line L12 outputs (pos−t+1,j)(\mathrm{pos} - t + 1, j) for the jj with Pj=T[pos−t+1…pos]P_j = T[\mathrm{pos} - t + 1 \ldots \mathrm{pos}], that is, the patterns of length tt that end at pos\mathrm{pos}.

Conversely, if a nonempty PjP_j ends at pos\mathrm{pos}, then PjRP_j^R is the prefix of UposU_{\mathrm{pos}} of length ∣Pj∣|P_j|, and every prefix of it is a node by Lemma 1, so ∣Pj∣≤g|P_j| \le g and the iteration t=∣Pj∣t = |P_j| outputs (pos−∣Pj∣+1,j)(\mathrm{pos} - |P_j| + 1, j). Each tt is visited once and each jj is in one out\mathrm{out} list, so no pair is output twice. □\square

Lemma 3 (the table). After line L6, for every character cc,

d[c]=min⁡({ℓ}∪{δ:1≤δ≤ℓ−1, ∃j:Pj≠ε, Pj[∣Pj∣−1−δ]=c}).d[c] = \min\Big(\{\ell\} \cup \{\delta : 1 \le \delta \le \ell - 1,\ \exists j: P_j \ne \varepsilon,\ P_j[|P_j| - 1 - \delta] = c\}\Big).

In particular 1≤d[c]≤ℓ1 \le d[c] \le \ell.

Proof. Line L5 sets every entry to ℓ\ell. Line L6 visits, for each nonempty PjP_j, the indices ii from ∣Pj∣−ℓ|P_j| - \ell to ∣Pj∣−2|P_j| - 2, which are exactly the indices ∣Pj∣−1−δ|P_j| - 1 - \delta for δ\delta from 11 to ℓ−1\ell - 1 (all valid, since ∣Pj∣≥ℓ|P_j| \ge \ell), and lowers d[Pj[i]]d[P_j[i]] to δ\delta when that is smaller. So each entry ends as the minimum of ℓ\ell and the distances found. □\square

Lemma 4 (safe shift). For every pos≤n−1\mathrm{pos} \le n - 1 and every δ\delta with 1≤δ<d[T[pos]]1 \le \delta < d[T[\mathrm{pos}]], no nonempty pattern ends at pos+δ\mathrm{pos} + \delta.

Proof. Suppose PjP_j ends at e=pos+δe = \mathrm{pos} + \delta with 1≤δ<d[T[pos]]1 \le \delta < d[T[\mathrm{pos}]]. By Lemma 3, δ<d[T[pos]]≤ℓ≤∣Pj∣\delta < d[T[\mathrm{pos}]] \le \ell \le |P_j|, so the occurrence T[e−∣Pj∣+1…e]T[e - |P_j| + 1 \ldots e] contains pos\mathrm{pos}, at the index ∣Pj∣−1−δ|P_j| - 1 - \delta of PjP_j. Hence Pj[∣Pj∣−1−δ]=T[pos]P_j[|P_j| - 1 - \delta] = T[\mathrm{pos}] with 1≤δ≤ℓ−11 \le \delta \le \ell - 1, and Lemma 3 gives d[T[pos]]≤δd[T[\mathrm{pos}]] \le \delta, a contradiction. □\square

Theorem. The claim holds.

Proof. Empty patterns. Line L1 outputs (s,j)(s, j) for every s∈{0,…,n}s \in \{0, \ldots, n\} and every empty PjP_j, which are exactly their occurrences, once each; no later line outputs an empty pattern, since the trie has none. If all patterns are empty, line L2 stops here.

Termination. By Lemma 3, each pass of line L13 increases pos\mathrm{pos} by at least 1, and the inner loop of line L10 runs at most pos+1\mathrm{pos} + 1 times since ii decreases from pos\mathrm{pos} and must stay ≥0\ge 0. So the loop of line L8 ends.

Nonempty patterns. Let e1<e2<⋯e_1 < e_2 < \cdots be the values of pos\mathrm{pos} at line L9. A nonempty pattern ends at some e≥∣Pj∣−1≥ℓ−1=e1e \ge |P_j| - 1 \ge \ell - 1 = e_1 with e≤n−1e \le n - 1. If ee is not one of the ete_t, there is a tt with et<e<et+1=et+d[T[et]]e_t < e < e_{t+1} = e_t + d[T[e_t]], or ee is beyond the last visited value ete_t, where et+d[T[et]]>n−1≥ee_t + d[T[e_t]] > n - 1 \ge e; in both cases Lemma 4 with pos=et\mathrm{pos} = e_t and δ=e−et\delta = e - e_t says that no pattern ends at ee. So every end position of a nonempty pattern is visited, each exactly once, and by Lemma 2 the readings at the visited positions output exactly the occurrences that end there, each once. Two different end positions give different pairs, so no pair is output twice. ■\blacksquare

Back to Set Horspool algorithm