Definition · The Horspool shift tableFor a pattern p of length m over an alphabet A, the shift table is defined for every c in A by: shift[c] = m − 1 − j, where j is the largest index with 0 ≤ j ≤ m − 2 and p[j] = c; and shift[c] = m if c does not occur among p[0..m−2]. Every entry lies between 1 and m.
Definition · Horspool matchingStart with the window at offset s = 0. Compare t[s+k] with p[k] for k = m − 1 downwards, stopping at the first disagreement or reporting a match when k falls below 0. Then set s to s + shift[t[s+m−1]] and repeat while s + m ≤ n. The shift is read from the text character under the window's LAST position, whatever position the comparison actually failed at.
Theorem · The shift skips no matchIf the window is at offset s and c = t[s+m−1], then no offset s' with s < s' < s + shift[c] can begin an occurrence of p.
Suppose an occurrence begins at such an s'. The character c sits at text position s + m − 1, which lies inside the occurrence's window because s' ≤ s + m − 1 ≤ s' + m − 1, at pattern index i = s + m − 1 − s', and 0 ≤ i ≤ m − 2 because s' > s.
So p[i] = c for some i ≤ m − 2, and therefore shift[c] is m − 1 − j for the largest such index j, giving i ≤ j and s + m − 1 − s' ≤ m − 1 − shift[c] + ... . Rearranged: s' ≥ s + m − 1 − j = s + shift[c], contradicting s' < s + shift[c].
If c does not occur in p[0..m−2] at all then no such i exists, so no occurrence can begin strictly after s and before s + m, which is the case shift[c] = m.
□
The lab checks that argument empirically on every keystroke rather than resting on it, because the failure mode of a skipping algorithm is to jump over an answer and return a shorter list of positions, and a shorter list of correct positions looks exactly like a correct answer. The match positions are compared with a scan that tests every offset.
Example · Fourteen alignments out of sixty-oneThe lab opens on the rain in spain stays mainly in the plain and never in the hills, 66 characters, searched for mainly. Naive matching would try 61 alignments; Horspool examines 14, at offsets 0, 4, 7, 13, 19, 24, 30, 36, 39, 43, 45, 51, 57 and 60. The shifts it takes are 4, 3, 6, 6, 5, 6, 6, 3, 4, 2, 6, 6, 3, 6 — seven of the fourteen are the full m. The single match is at offset 24, and it is examined.
Those fourteen alignments cost 19 comparisons in total, and the breakdown is lopsided: thirteen of them fail on their very first comparison and cost 1 each, and the fourteenth is the match at offset 24, which compares all 6. Thirteen plus six is 19.
Those 19 comparisons landed on only 18 distinct positions of the text, because position 24 is compared twice — once as the last position of the window at offset 19, and again inside the matching window. So 48 of the 66 characters of this text were never compared to anything at all. That is the property the algorithm is famous for, and it is why characters skipped entirely is the counter the panel leads with.
Why the average shift is 4.71 and what it is an average of
The panel's average shift is the sum of the shifts divided by the number of alignments examined, which is 66/14, printed as the exact fraction 33/7. The sum of the shifts is the text length because the window walks from 0 past the end exactly once, so that average is n over the number of alignments — a different quantity from the average over the alphabet, which would be a fact about the pattern alone.
The letters that shift the full six, including one of the pattern's own
The shift table has m → 5, a → 4, i → 3, n → 2, l → 1, and 6 for the space and for d, e, h, p, r, s, t, v and y. The y is worth a second look, because y is in the pattern — it is p[5], the last character. The table is built from p[0..4] only, so y is absent from it and gets the default m. That is deliberate: if the window's last character is y and the comparison still failed, the failure was further left, and no alignment before s + m can succeed either.
The l → 1 entry is the other end of the same table. l occurs at p[4], one place before the end, so a window ending in l can only be advanced by one. A pattern whose penultimate character is common in the text therefore loses most of the benefit, which is the mechanism the next page pushes to its limit.
What did not improve
Naive matching takes 66 comparisons on this text and KMP takes 66 as well. Horspool takes 19, which is better by a factor of three and a half — and its worst case is m(n − m + 1), exactly the naive matcher's, while KMP's remains 2n. So on the page's own numbers the fastest algorithm here has the weakest guarantee of the three, and the slowest has the strongest.
There is no contradiction and there is also no ranking. Three measurements and three bounds are six statements about different things, and the only ones that hold for texts nobody has typed yet are the bounds.