KMP for comparison

For comparison with the previous post, this is how KMP would look like:

[cce_c]
static void kmp_search(char * key, char * buffer)
{
unsigned long n = strlen(buffer);
unsigned long m = strlen(key);
unsigned long ba[m];
ba[0] = 0;
for (unsigned long i = 1; i < m; ++i) {
unsigned long b = ba[i-1];
while (b > 0 && key[i] != key[b])
b = ba[b-1];
ba[i] = (key[i] == key[b]) ? b + 1 : 0;
}
unsigned long i, j; i = j = 0;
while (i < n – m + j) {
while (buffer[i] == key[j] && j < m) {
++i; ++j;
}
if (j == m) printf(“%lu\n”, i – m);
if (j == 0) ++i;
else j = ba[j-1];
}
}
[/cce_c]

This algorithm is doing essentially the same thing as the border-array based algorithm, but the two loops look very different and you need to get both of the right. The previous algorithm also has two loops, but they are doing the same thing, so if you get one of the right you will very easily get the other right as well.

Linear time exact pattern matching

Yesterday, Storm and I had exams in String Algorithms. During the exams we came up with a simple linear time exact pattern matching algorithm that is very similar to the Knuth–Morris–Pratt algorithm but a little bit simpler.

The Knuth-Morris-Pratt algorithm is based on border arrays. A border of a string \(x\) is any substring that is both a prefix and a postfix of \(x\). The border array for \(x\) is an array that for each index \(i\) holds the length of the longest border for prefix \(x[1\ldots i]\). You can compute the border array for a string “key” using the algorithm below:

[cce_c]
unsigned long m = strlen(key);
unsigned long ba[m];
ba[0] = 0;
for (unsigned long i = 1; i < m; ++i) {
unsigned long b = ba[i-1];
while (b > 0 && key[i] != key[b])
b = ba[b-1];
ba[i] = (key[i] == key[b]) ? b + 1 : 0;
}
[/cce_c]

There is a double loop, but you can show that the total number of iterations in the inner loop never exceeds the total number of iterations of the other loop, so the algorithm computes the border array in time \(O(m)\) where \(m\) is the length of the string.

The Knuth-Morris-Pratt algorithm computes the border array for the key it searches for and then has a loop through the string it searches in where it uses the border array to step forward when it sees a mismatch. You can, however, also search in linear time just using border arrays. This is something Storm has used as an exercises in our class for years now. The idea is this, if you want to search for pattern \(p\) in string \(x\), then you can construct the string \(p\#x\) where # is a sentinel symbol that is not found in \(p\) and \(x\). If you build the border array for this string, you can find all occurrences of \(p\) in \(x\) by finding the indices \(i>m\) where the border array contains \(m\) and then report \(i-m+1\).

What we realised yesterday while asking questions at the exam was that you never actually use the border array beyond the first \(m\) entries in this algorithm. You just need to know how long a border would be at any point, but it can never grow beyond \(m\) by construction, so there is no need to store it. Instead, we can just build the border array for the key we search for and then pretend that we build the border array for the rest, but really just keep track of the length of it.

A complete search function based on this can be written in a few lines of C:

[cce_c]
static void ba_search(char * key, char * buffer)
{
unsigned long n = strlen(buffer);
unsigned long m = strlen(key);
unsigned long ba[m];
ba[0] = 0;
for (unsigned long i = 1; i < m; ++i) {
unsigned long b = ba[i-1];
while (b > 0 && key[i] != key[b])
b = ba[b-1];
ba[i] = (key[i] == key[b]) ? b + 1 : 0;
}
unsigned long b = 0;
for (unsigned long i = 0; i < n; ++i) {
while (b > 0 && buffer[i] != key[b])
b = ba[b-1];
b = (buffer[i] == key[b]) ? b + 1 : 0;
if (b == m)
printf(“%lu\n”, i – m + 1);
}
}
[/cce_c]

The algorithm is essentially just Knuth-Morris-Pratt. It uses the border array for the key in exactly the same way. It is just slightly simpler because it doesn’t consider the search part of the algorithm as different from the preprocessing. Both the loops in this function are essentially doing the same thing—the only reason they are different is that one builds the border array and only looks at the key while the other only keeps track of border lengths and looks at the string we search in.

Beginner’s Guide to Todoist

Back over Easter Amir Salihefendic and I wrote a guide to Todoist, the todo list manager that his company makes. It took a little longer for us to finally make the guide public—Amir had some of his designers make a better cover than I had hacked together with my quite limited skills and had one of his guys copyedit it first—but now you can get it on Amazon and if you get it before June 4 you can even get it for free.