Rabin-Karp Matcher

Posted by Dustin Boston .

Source Code Listing

code.ts

/**
 * ```
 * RABIN-KARP-MATCHER(T, P, d, q)
 *   n = T.length
 *   m = P.length
 *   h = d^m-1 mod q
 *   p = 0
 *   t_0 = 0
 *
 *   for i = 1 to m               // preprocessing
 *     p = (d * p + P[i]) mod q
 *     t_0 = (d * t_0 + T[i]) mod q
 *
 *   for s = 0 to n - m           // matching
 *     if p == t_s
 *       if P[1..m] == T[s + 1..s + m]
 *         print "Pattern occurs with shift" s
 *     if s < n - m
 *       t_s+1 = (d * (t_s - T[s + 1] * h) + T[s + m + 1]) mod q
 * ```
 */
export function rabinKarpMatcher(
  T: string, // Text
  P: string, // Pattern
  d: number, // Radix
  q: number, // Prime
) {
  const n = T.length;
  const m = P.length;
  const h = d ** (m - 1) % q;
  let p = 0; // Code point of a given letter
  let t = 0; // Code point of a given letter

  // Preprocessing
  for (let i = 0; i < m; i++) {
    p = (d * p + (P.codePointAt(i) ?? 0)) % q;
    t = (d * t + (T.codePointAt(i) ?? 0)) % q;
  }

  // Matching
  for (let s = 0; s <= n - m; s++) {
    if (p === t && P.slice(0, m) === T.slice(s, s + m)) {
      console.log(`Pattern occurs with shift ${s}`);
      return [P.slice(0, m), s];
    }

    if (s < n - m) {
      let newt =
        (d * (t - (T.codePointAt(s) ?? 0) * h) + (T.codePointAt(s + m) ?? 0)) %
        q;

      if (newt < 0) {
        newt += q;
      }

      t = newt;
    }
  }
}

test.ts

import {expect, test} from "bun:test";
import {rabinKarpMatcher} from "./code.ts";

test(`find pattern "aab" in "acaabc"`, () => {
  const actual = rabinKarpMatcher("acaabc", "aab", 3, 13) ?? [];
  const pattern = actual[0];
  const shift = actual[1];

  expect(pattern).toEqual("aab");
  expect(shift).toEqual(2);
});