I wrote an implementation of the Karp-Rabin string matching algorithm in Java 7, based on the discussion in Section 32.2 of Introduction to Algorithms (CLRS). Clearly, I need more experience with numeric programming, because I spent two days wrestling with overflow issues and floating point error, but I believe it works now.
Of course, anything goes, but I'm especially looking for feedback on the following points:
- Are there any cases left where it would fail?
- How can I improve the design?
- How can I make the code more readable?
- How can I make the code more robust?
- Did I miss any glaring performance traps?
Here's the class constructor and the match
method, which do the bulk of the work.
public class KarpRabin {
final long q, b;
public static final int NONE = -1;
Map<Character, Integer> digitMap;
public KarpRabin(String alphabet) {
digitMap = new HashMap<>();
for (int i = 0; i < alphabet.length(); ++i) {
digitMap.put(alphabet.charAt(i), i);
}
q = 26900927; // A prime such that b*q can reasonably fit in
// a word for most values of b.
b = alphabet.length();
if (b >= q) {
throw new IllegalArgumentException("b cannot be larger than " + q);
}
}
public int match(String pattern, String text) {
// Returns location of first match of pattern in text.
if (pattern.length() > text.length()) {
throw new IllegalArgumentException("Pattern longer than text.");
}
int[] T = digitValue(text);
int[] P = digitValue(pattern);
long h, p, t;
p = t = 0;
h = expt(b, pattern.length()-1).mod(BigInteger.valueOf(q)).longValue(); // See note [7].
// Calculate fingerprint of pattern and of first
// pattern.length-length group in text.
for (int i = 0; i < pattern.length(); ++i) {
p = (b*p + P[i]) % q;
t = (b*t + T[i]) % q;
}
for (int s = 0; s < text.length() - pattern.length(); ++s) {
if (p == t) {
if (pattern.equals(text.substring(s, s+pattern.length()))) {
return s;
}
}
assert s < T.length && s + pattern.length() < T.length :
"s is " + s + " and s+pattern.length() is " +
(s + pattern.length());
t = (b * (t - T[s]*h) + T[s + pattern.length()]) % q;
// Handle Java's modulus behavior; see note[2].
t += q;
t %= q;
assert t >= 0 : "t is below 0, has value " + t;
}
// See note [4].
if (p == t) {
if (pattern.equals(text.substring(
text.length() - pattern.length(), text.length())))
return text.length() - pattern.length();
}
return NONE;
}
Here's a gist with the rest of the code, including the private helper methods.
Although the current code is Java 7, I plan to switch to Java 8 in the near future, so suggestions for where I could make good use of Java 8 features is also welcome.