String similarity
rapid-fuzzy has seven edit-distance and similarity metrics and four token-based ratios. Each comes in three forms: a single comparison, a *Batch function for many pairs, and a *Many function for one string against many.
Distance and similarity metrics
Section titled “Distance and similarity metrics”import { levenshtein, normalizedLevenshtein, damerauLevenshtein, jaro, jaroWinkler, sorensenDice, hamming, normalizedHamming, indel, normalizedIndel,} from 'rapid-fuzzy';
levenshtein('kitten', 'sitting'); // 3normalizedLevenshtein('kitten', 'sitting'); // 0.571 (1 - 3 / 7)damerauLevenshtein('ab', 'ba'); // 1 (an adjacent transposition is one edit)jaro('martha', 'marhta'); // 0.944jaroWinkler('MARTHA', 'MARHTA'); // 0.961 (Jaro plus a bonus for a common prefix)sorensenDice('night', 'nacht'); // 0.25hamming('karolin', 'kathrin'); // 3 (null if the lengths differ)normalizedHamming('karolin', 'kathrin'); // 0.571 (null if the lengths differ)indel('abc', 'ac'); // 1 (insertions and deletions only)normalizedIndel('kitten', 'sitting'); // 0.615| Function | Returns |
|---|---|
levenshtein, damerauLevenshtein, indel |
The number of edits (an integer) |
hamming |
The number of differing positions, or null if the lengths differ |
normalizedLevenshtein, normalizedIndel, jaro, jaroWinkler, sorensenDice |
A similarity from 0 (nothing in common) to 1 (identical) |
normalizedHamming |
A similarity from 0 to 1, or null if the lengths differ |
These functions compare the Unicode code points of the strings exactly as given. They do not ignore case or whitespace, and they do not normalize Unicode: levenshtein('Cafe', 'cafe') is 1, and a precomposed é differs from e plus a combining accent. Lower-case or normalize the input first if that matters to you. sorensenDice removes whitespace and compares character bigrams.
Token-based ratios
Section titled “Token-based ratios”The token-based ratios come from Python’s RapidFuzz. They lower-case their input and split it on whitespace, so they ignore case and word order:
import { tokenSortRatio, tokenSetRatio, partialRatio, weightedRatio } from 'rapid-fuzzy';
// Sorts the words before comparingtokenSortRatio('New York Mets', 'Mets New York'); // 1
// Compares shared and extra words separatelytokenSetRatio('Great Gatsby', 'The Great Gatsby by Fitzgerald'); // 1
// The shorter string against every same-length window of the longer onepartialRatio('hello', 'hello world'); // 1
// The highest of normalizedLevenshtein, tokenSortRatio, tokenSetRatio and partialRatioweightedRatio('John Smith', 'Smith John'); // 1Scores are on a 0–1 scale. They are not always equal to RapidFuzz’s or fuzzball’s: weightedRatio does not scale by length difference the way WRatio does. See the fuzzball migration guide. partialRatio and weightedRatio are expensive on long strings; see Performance.
Many strings at once
Section titled “Many strings at once”Every metric has two bulk variants, which make one call across the JavaScript–Rust boundary instead of one call per pair:
*Many(reference, candidates)compares one string against many. It reuses the work that depends only on the reference, which makes it the fastest way to rank candidates.*Batch(pairs)computes the metric for an array of[a, b]pairs.
import { levenshteinBatch, levenshteinMany, jaroWinklerMany } from 'rapid-fuzzy';
levenshteinBatch([ ['kitten', 'sitting'], ['hello', 'help'],]); // → [3, 2]
levenshteinMany('kitten', ['sitting', 'kittens', 'kitchen']); // → [3, 1, 2]
// A threshold skips candidates that cannot qualifylevenshteinMany('kitten', candidates, 3); // a candidate more than 3 edits away gets 4jaroWinklerMany('MARTHA', candidates, 0.8); // a candidate below 0.8 gets 0levenshteinMany over 1,000 short candidates was about 1.5x faster than calling levenshtein in a loop. For a handful of short pairs, *Batch is not faster than single calls.
A *Batch pair that is not exactly two strings throws, and so does a NaN threshold.
Typed-array results
Section titled “Typed-array results”Each *Many function has a variant that returns a typed array instead of number[], which avoids boxing every number and transfers to a worker without copying (postMessage(result, [result.buffer])):
*ManyU32returns aUint32Arrayof distances:levenshteinManyU32,damerauLevenshteinManyU32,indelManyU32,hammingManyU32.*ManyF64returns aFloat64Arrayof similarities:jaroManyF64,jaroWinklerManyF64,normalizedLevenshteinManyF64,normalizedIndelManyF64,normalizedHammingManyF64,sorensenDiceManyF64and the four token ratios.
A typed array cannot hold null. hammingManyU32 therefore returns 0xffffffff, and normalizedHammingManyF64 returns NaN, for a candidate of a different length or one filtered out by the threshold.
import { levenshteinManyU32, jaroWinklerManyF64 } from 'rapid-fuzzy';
levenshteinManyU32('kitten', ['sitting', 'kittens', 'kitchen']); // Uint32Array [3, 1, 2]jaroWinklerManyF64('kitten', ['sitting', 'kittens', 'kitchen']); // Float64Array [0.746, 0.971, 0.894]