Jaro-Winkler String Similarity
Score string similarity with matching windows, transpositions and a common-prefix boost for fuzzy name matching.
Code
export function jaroSimilarity(a: string, b: string): number {
if (a === b) return 1;
if (a.length === 0 || b.length === 0) return 0;
const window = Math.max(0, Math.floor(Math.max(a.length, b.length) / 2) - 1);
const aMatches = new Array<boolean>(a.length).fill(false);
const bMatches = new Array<boolean>(b.length).fill(false);
let matches = 0;
for (let i = 0; i < a.length; i += 1) {
const start = Math.max(0, i - window);
const end = Math.min(i + window + 1, b.length);
for (let j = start; j < end; j += 1) {
if (bMatches[j] || a[i] !== b[j]) continue;
aMatches[i] = true;
bMatches[j] = true;
matches += 1;
break;
}
}
if (matches === 0) return 0;
let transpositions = 0;
let cursor = 0;
for (let i = 0; i < a.length; i += 1) {
if (!aMatches[i]) continue;
while (!bMatches[cursor]) cursor += 1;
if (a[i] !== b[cursor]) transpositions += 1;
cursor += 1;
}
return (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
}
export function jaroWinkler(a: string, b: string, prefixScale = 0.1): number {
if (prefixScale < 0 || prefixScale > 0.25) {
throw new RangeError("prefixScale must be between 0 and 0.25");
}
const jaro = jaroSimilarity(a, b);
let prefix = 0;
const limit = Math.min(4, a.length, b.length);
while (prefix < limit && a[prefix] === b[prefix]) prefix += 1;
return jaro + prefix * prefixScale * (1 - jaro);
}Report a problem with this exampleOpens a pre-filled issue on GitHub.