Algorithm Search⌘K

Proofs: Bird-Baker algorithm

These are the human proofs of Bird-Baker algorithm, at full length.

Bird-Baker outputs exactly the occurrences of the patterns full

This proof shows: correctness, termination.

Claim. For every rectangular text TT with n1n_1 rows and n2n_2 columns and every list of rectangular patterns P0,…,Pk−1P_0, \ldots, P_{k-1}, BIRD-BAKER stops and outputs each triple (r,c,j)(r, c, j) such that PjP_j occurs with its top-left corner at (r,c)(r, c) exactly once, and no other triple.

Notation. For a set WW of words, its trie has a node for each prefix of a word of WW; spell(v)\mathrm{spell}(v) is the prefix of node vv, and ∣v∣|v| is its length. The failure link fail(v)\mathrm{fail}(v) of a node v≠v \ne root is the node of the longest proper suffix of spell(v)\mathrm{spell}(v) that is a node. The step from vv by a symbol aa follows failure links from vv until a node with a child by aa is found, and goes to that child, or to the root if no such node exists. The output list of vv is the list of indices of the words equal to spell(v)\mathrm{spell}(v), followed by the output list of fail(v)\mathrm{fail}(v).

Lemma 1 (Aho-Corasick). Let ss be a sequence and vv the state after the steps for s[0],s[1],…s[0], s[1], \ldots, from the root. Then spell(v)\mathrm{spell}(v) is the longest suffix of ss that is a prefix of a word of WW, and the output list of vv holds each index ii with WiW_i a suffix of ss exactly once.

Proof. First, the nodes on the failure chain v,fail(v),fail(fail(v)),…v, \mathrm{fail}(v), \mathrm{fail}(\mathrm{fail}(v)), \ldots spell exactly the suffixes of spell(v)\mathrm{spell}(v) that are nodes, in decreasing length: each link goes to the longest shorter one, so none is skipped. Now use induction on ∣s∣|s|; for ss empty the state is the root. Let vv satisfy the claim for ss, and read aa. A suffix of sasa that is a node and is not empty has the form yay a, where yy is a suffix of ss and a node, so ∣y∣≤∣v∣|y| \le |v| and yy is a suffix of spell(v)\mathrm{spell}(v), so yy is on the failure chain of vv. The step walks this chain in decreasing length and stops at the first yy with a child by aa, which gives the longest yaya; if there is none, the answer is the empty suffix, the root. For the output, a word WiW_i that is a suffix of ss is a node and a suffix of ss, so it is a suffix of spell(v)\mathrm{spell}(v) and is spelled by exactly one node of the failure chain of vv. The output list of vv is the concatenation of the lists of the words equal to the strings of these distinct nodes, so it holds ii exactly once. Conversely each index on it is of a word that is a suffix of spell(v)\mathrm{spell}(v), hence of ss. □\square

Fix the width ww of one pass, and let PjP_j for j∈Jj \in J be the nonempty patterns of width ww. Let XX be the set of their rows, all of length ww, with distinct names, and ⊥ a symbol that is not a name.

Lemma 2 (row names). After line L7 for row rr, for each cc with w−1≤c<n2w - 1 \le c < n_2, L[c]L[c] is the name of T[r][c−w+1…c]T[r][c - w + 1 \ldots c] if this string is in XX, and ⊥ otherwise.

Proof. By Lemma 1 for W=XW = X, the state after T[r][0…c]T[r][0 \ldots c] spells the longest suffix yy of T[r][0…c]T[r][0 \ldots c] that is a prefix of a row in XX, and ∣y∣≤w|y| \le w. If x=T[r][c−w+1…c]x = T[r][c - w + 1 \ldots c] is in XX, then xx is such a suffix of length ww, so y=xy = x, the state has depth ww, and line L7 writes the name of xx. If the state has depth ww, then yy is a prefix of length ww of a row of length ww, so yy is in XX, and y=T[r][c−w+1…c]y = T[r][c - w + 1 \ldots c] because ∣y∣=w|y| = w. So line L7 writes a name exactly when x∈Xx \in X, and then the name of xx. □\square

Lemma 3 (reduction). For j∈Jj \in J and 0≤r0≤n1−mj0 \le r_0 \le n_1 - m_j, 0≤c0≤n2−w0 \le c_0 \le n_2 - w, the pattern PjP_j occurs at (r0,c0)(r_0, c_0) if and only if, with c=c0+w−1c = c_0 + w - 1, the names L[c]L[c] written for the rows r0,…,r0+mj−1r_0, \ldots, r_0 + m_j - 1 spell RjR_j.

Proof. PjP_j occurs at (r0,c0)(r_0, c_0) if and only if T[r0+i][c0…c]=Pj[i]T[r_0 + i][c_0 \ldots c] = P_j[i] for each i<mji < m_j. Since Pj[i]∈XP_j[i] \in X, Lemma 2 shows that this holds if and only if the name written at (r0+i,c)(r_0 + i, c) is the name of Pj[i]P_j[i]: if the strings are equal, that name is written; if the name of Pj[i]P_j[i] is written, the text string is the row with that name, which is Pj[i]P_j[i] because different rows have different names. □\square

Lemma 4 (columns). In the pass for width ww, line L10 at row rr and column cc outputs (r−mj+1,c−w+1,j)(r - m_j + 1, c - w + 1, j) exactly for the j∈Jj \in J such that RjR_j is a suffix of the column string S=L0[c]⋯Lr[c]S = L_0[c] \cdots L_r[c], where Lt[c]L_t[c] is the name written at (t,c)(t, c), and each such jj once.

Proof. Line L5 starts q[c]q[c] at the root, and line L9 feeds it L0[c],L1[c],…L_0[c], L_1[c], \ldots in order, one symbol per row, since column c≥w−1c \ge w - 1 is visited at every row. A ⊥ is in no RjR_j, so the step sends the state to the root, which is right by Lemma 1 because no nonempty suffix of a sequence ending in ⊥ is a prefix of some RjR_j. Apply Lemma 1 with W=(Rj)j∈JW = (R_j)_{j \in J}. □\square

Theorem. The claim holds.

Proof. Termination: every loop runs over a finite range, and each step of an automaton follows finitely many failure links because each one lowers the depth. Line L1 outputs, for each pattern with no rows or with empty rows, exactly the triples of its occurrences given in the problem statement, once each, and such patterns take part in no pass. Take a nonempty pattern PjP_j of width ww and a corner (r0,c0)(r_0, c_0). A triple (r0,c0,j)(r_0, c_0, j) can be output only in the pass for width ww, at row r=r0+mj−1r = r_0 + m_j - 1 and column c=c0+w−1c = c_0 + w - 1, since line L10 determines r0r_0 and c0c_0 from rr, cc and jj. At that row and column, by Lemma 4 it is output once if RjR_j is a suffix of L0[c]⋯Lr[c]L_0[c] \cdots L_r[c], that is, if the rows r0,…,rr_0, \ldots, r of column cc spell RjR_j (which needs r0≥0r_0 \ge 0), and not otherwise. By Lemma 3 this happens exactly when PjP_j occurs at (r0,c0)(r_0, c_0). A corner with c0>n2−wc_0 > n_2 - w or r0>n1−mjr_0 > n_1 - m_j never arises, since c≤n2−1c \le n_2 - 1 and r≤n1−1r \le n_1 - 1. So every occurrence is output once and nothing else is output. ■\blacksquare

Back to Bird-Baker algorithm