You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
const maxDistance = 3;
function editDistance(a, b) { // https://en.wikipedia.org/wiki/Damerau–Levenshtein_distance
// Calculating optimal string alignment distance, no substring is edited more than once.
// (Simple implementation.)
// Quick early exit, return worst case.
if (Math.abs(a.length - b.length) > maxDistance) return Math.max(a.length, b.length);
// distance between prefix substrings of a and b
const d = [];
// pure deletions turn a into empty string
for (let i = 0; i <= a.length; i++) { d[i] = [i]; } // pure insertions turn empty string into b
for (let j = 0; j <= b.length; j++) { d[0][j] = j; }
// fill matrix
for (let j = 1; j <= b.length; j++) { for (let i = 1; i <= a.length; i++) { let cost = 1; if (a[i - 1] === b[j - 1]) { cost = 0; } else { cost = 1; } d[i][j] = Math.min( d[i - 1][j] + 1, // deletion
d[i][j - 1] + 1, // insertion
d[i - 1][j - 1] + cost // substitution
); // transposition
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) { d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1); } } }
return d[a.length][b.length]; }
/** * Find close matches, restricted to same number of edits. * * @param {string} word * @param {string[]} candidates * @returns {string} */
function suggestSimilar(word, candidates) { if (!candidates || candidates.length === 0) return ''; // remove possible duplicates
candidates = Array.from(new Set(candidates));
const searchingOptions = word.startsWith('--'); if (searchingOptions) { word = word.slice(2); candidates = candidates.map(candidate => candidate.slice(2)); }
let similar = []; let bestDistance = maxDistance; const minSimilarity = 0.4; candidates.forEach((candidate) => { if (candidate.length <= 1) return; // no one character guesses
const distance = editDistance(word, candidate); const length = Math.max(word.length, candidate.length); const similarity = (length - distance) / length; if (similarity > minSimilarity) { if (distance < bestDistance) { // better edit distance, throw away previous worse matches
bestDistance = distance; similar = [candidate]; } else if (distance === bestDistance) { similar.push(candidate); } } });
similar.sort((a, b) => a.localeCompare(b)); if (searchingOptions) { similar = similar.map(candidate => `--${candidate}`); }
if (similar.length > 1) { return `\n(Did you mean one of ${similar.join(', ')}?)`; } if (similar.length === 1) { return `\n(Did you mean ${similar[0]}?)`; } return ''; }
exports.suggestSimilar = suggestSimilar;
|