Home Reference Source Repository

src/tools/testEditDistance.js

/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule testEditDistance
 * @flow
 *
 */

/* eslint-disable no-shadow */

/**
 * @internal
 *
 * Determines whether the edit distance between two strings is at or below the
 * specified threshold distance, using the approach described by Ukkonen (1985)
 * in "Algorithms for Approximate String Matching"[0] and then improved upon by
 * Berghel and Roach (1996) in "An Extension of Ukkonen's Enhanced Dynamic
 * Programming ASM Algorithm"[1].
 *
 * Given two strings of length `m` and `n` respectively, and threshold `t`,
 * uses `O(t*min(m,n))` time and `O(min(t,m,n))` space.
 *
 * @see [0]: http://www.cs.helsinki.fi/u/ukkonen/InfCont85.PDF
 * @see [1]: http://berghel.net/publications/asm/asm.pdf
 */
function testEditDistance(a: string, b: string, threshold: number): boolean {
  // Ensure `b` is at least as long as `a`, swapping if necessary.
  let m = a.length;
  let n = b.length;
  if (n < m) {
    [n, m] = [m, n];
    [b, a] = [a, b];
  }
  if (!m) {
    return n <= threshold;
  }

  const zeroK = n;
  const maxK = zeroK * 2 + 1;
  const fkp = Array.from(Array(maxK), () => []);

  for (let k = -zeroK; k < 0; k++) {
    const p = -k - 1;
    fkp[k + zeroK][p + 1] = -k - 1;
    fkp[k + zeroK][p] = -Infinity;
  }
  fkp[zeroK][0] = -1;
  for (let k = 1; k <= zeroK; k++) {
    const p = k - 1;
    fkp[k + zeroK][p + 1] = -1;
    fkp[k + zeroK][p] = -Infinity;
  }

  // This loop is the alternative form suggested in the afterword of Berghel &
  // Roach.
  let p = n - m - 1;
  do {
    if (p > threshold) {
      return false;
    }
    p++;
    for (let i = Math.floor((p - (n - m)) / 2); i >= 1; i--) {
      f(n - m + i, p - i);
    }
    for (let i = Math.floor((n - m + p) / 2); i >= 1; i--) {
      f(n - m - i, p - i);
    }
    f(n - m, p);
  } while (fkp[n - m + zeroK][p] !== m);

  return true;

  function f(k, p) {
    let t = fkp[k + zeroK][p] + 1;
    let t2 = t;

    // Check for transposed characters.
    if (a[t - 1] === b[k + t] && a[t] === b[k + t - 1]) {
      t2 = t + 1;
    }

    t = Math.max(
      t,
      fkp[k - 1 + zeroK][p],
      fkp[k + 1 + zeroK][p] + 1,
      t2
    );

    while (a[t] === b[t + k] && t < Math.min(m, n - k)) {
      t++;
    }
    fkp[k + zeroK][p + 1] = t;
  }
}

module.exports = testEditDistance;