Algorithm Search⌘K

Proofs: Wu-Manber algorithm

These are the human proofs of Wu-Manber algorithm, at full length.

Wu-Manber 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], patterns P0,…,Pk−1P_0, \ldots, P_{k-1} and block size B≥1B \ge 1, WU-MANBER 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. After line L3, ℓ≥1\ell \ge 1 is the shortest nonempty length and 1≤B≤ℓ1 \le B \le \ell. For a nonempty PjP_j, the cut pattern is P^j=Pj[0…ℓ−1]\hat P_j = P_j[0 \ldots \ell - 1]. A window is identified by pos\mathrm{pos}; its start is s(pos)=pos−ℓ+1s(\mathrm{pos}) = \mathrm{pos} - \ell + 1 and its block is X(pos)=T[pos−B+1…pos]X(\mathrm{pos}) = T[\mathrm{pos} - B + 1 \ldots \mathrm{pos}], which is inside the text when ℓ−1≤pos≤n−1\ell - 1 \le \mathrm{pos} \le n - 1 since B≤ℓB \le \ell.

Lemma 1 (the tables). After line L6, for every block XX:

  1. SHIFT[X]=min⁡({ℓ−B+1}∪{ℓ−q:B≤q≤ℓ, ∃j:P^j[q−B…q−1]=X})\mathrm{SHIFT}[X] = \min\big(\{\ell - B + 1\} \cup \{\ell - q : B \le q \le \ell,\ \exists j: \hat P_j[q - B \ldots q - 1] = X\}\big), so 0≤SHIFT[X]≤ℓ−B+10 \le \mathrm{SHIFT}[X] \le \ell - B + 1;
  2. HASH[X]={j:Pj≠ε, P^j[ℓ−B…ℓ−1]=X}\mathrm{HASH}[X] = \{ j : P_j \ne \varepsilon,\ \hat P_j[\ell - B \ldots \ell - 1] = X \};
  3. PREFIX[j]=Pj[0…B−1]\mathrm{PREFIX}[j] = P_j[0 \ldots B - 1] for every nonempty PjP_j.

Proof. Line L4 sets every entry of SHIFT to ℓ−B+1\ell - B + 1, and line L5 lowers the entry of each block of each cut pattern, ending at qq, to ℓ−q\ell - q when smaller; the blocks visited are exactly those of the set in item 1. Line L6 appends each nonempty index once to the list of its last cut block, and stores the prefix. □\square

Lemma 2 (a zero-shift window). Let SHIFT[X(pos)]=0\mathrm{SHIFT}[X(\mathrm{pos})] = 0 and s=s(pos)s = s(\mathrm{pos}). Lines L13 and L14 output exactly the pairs (s,j)(s, j) with PjP_j nonempty and occurring at ss, each once.

Proof. If a nonempty PjP_j occurs at ss, then s+∣Pj∣≤ns + |P_j| \le n, T[s…s+ℓ−1]=P^jT[s \ldots s + \ell - 1] = \hat P_j, and so X(pos)=T[pos−B+1…pos]=P^j[ℓ−B…ℓ−1]X(\mathrm{pos}) = T[\mathrm{pos} - B + 1 \ldots \mathrm{pos}] = \hat P_j[\ell - B \ldots \ell - 1]. By Lemma 1, j∈HASH[X(pos)]j \in \mathrm{HASH}[X(\mathrm{pos})]. The three tests of line L14 succeed: the first BB characters agree because B≤ℓ≤∣Pj∣B \le \ell \le |P_j|, the length fits, and the whole pattern agrees. So (s,j)(s, j) is output. Conversely, line L14 outputs (s,j)(s, j) only after it has checked s+∣Pj∣≤ns + |P_j| \le n and T[s…s+∣Pj∣−1]=PjT[s \ldots s + |P_j| - 1] = P_j, so the pair is an occurrence. Each jj appears once in HASH[X(pos)]\mathrm{HASH}[X(\mathrm{pos})], so the pair is output once by this window. □\square

Lemma 3 (safe shift). Let d=SHIFT[X(pos)]>0d = \mathrm{SHIFT}[X(\mathrm{pos})] > 0 and s=s(pos)s = s(\mathrm{pos}). No nonempty pattern occurs at any start s+δs + \delta with 0≤δ<d0 \le \delta < d.

Proof. Suppose PjP_j occurs at s+δs + \delta with 0≤δ<d0 \le \delta < d. Then T[s+δ…s+δ+ℓ−1]=P^jT[s + \delta \ldots s + \delta + \ell - 1] = \hat P_j, and this range ends at pos+δ\mathrm{pos} + \delta.

  • If δ≤ℓ−B\delta \le \ell - B, let q=ℓ−δq = \ell - \delta, so B≤q≤ℓB \le q \le \ell. The block X(pos)X(\mathrm{pos}) occupies pos−B+1…pos\mathrm{pos} - B + 1 \ldots \mathrm{pos}, which is the range of indices q−B…q−1q - B \ldots q - 1 of P^j\hat P_j in this occurrence, since s+δ+q−1=poss + \delta + q - 1 = \mathrm{pos} and q−B≥0q - B \ge 0. So P^j[q−B…q−1]=X(pos)\hat P_j[q - B \ldots q - 1] = X(\mathrm{pos}), and Lemma 1 gives d≤ℓ−q=δd \le \ell - q = \delta, a contradiction.
  • If δ≥ℓ−B+1\delta \ge \ell - B + 1, then d>δ≥ℓ−B+1d > \delta \ge \ell - B + 1, which contradicts d≤ℓ−B+1d \le \ell - B + 1 from Lemma 1. □\square

Theorem. The claim holds.

Proof. Empty patterns. Line L1 outputs (s,j)(s, j) for all s∈{0,…,n}s \in \{0, \ldots, n\} and every empty PjP_j, exactly their occurrences, once each. Later lines output only nonempty patterns, since only they are in HASH. If all patterns are empty, line L2 stops.

Termination. Each pass of the loop of line L8 increases pos\mathrm{pos} by SHIFT[X]≥1\mathrm{SHIFT}[X] \ge 1 at line L10 or by 1 at line L15, and the inner loop of line L13 is over a finite list. So the loop ends.

Nonempty patterns. Let pos1<pos2<⋯<posr\mathrm{pos}_1 < \mathrm{pos}_2 < \cdots < \mathrm{pos}_r be the windows visited, with pos1=ℓ−1\mathrm{pos}_1 = \ell - 1, so the starts st=s(post)s_t = s(\mathrm{pos}_t) begin at s1=0s_1 = 0. Each window outputs only pairs with the start sts_t, so different windows output different pairs, and by Lemma 2 no pair is output twice. Let PjP_j be nonempty and occur at ss; then 0≤s≤n−∣Pj∣≤n−ℓ0 \le s \le n - |P_j| \le n - \ell. Let tt be the largest index with st≤ss_t \le s; it exists since s1=0s_1 = 0. The step taken after window tt carries the start from sts_t to a value above ss: if window t+1t + 1 exists, its start is above ss by the choice of tt; if t=rt = r, the loop ended because the next value of pos\mathrm{pos} exceeds n−1n - 1, so the next start exceeds n−ℓ≥sn - \ell \ge s.

  • If window tt had a zero shift, the next start is st+1>ss_t + 1 > s, so s=sts = s_t and Lemma 2 outputs (s,j)(s, j).
  • If window tt had a shift d>0d > 0, the next start, or the end of the loop, lies beyond ss, so st≤s<st+ds_t \le s < s_t + d, and Lemma 3 says that no nonempty pattern occurs at ss, a contradiction.

So every occurrence of a nonempty pattern is output exactly once, and only occurrences are output. ■\blacksquare

Back to Wu-Manber algorithm