fix ampersand ordering bug for fuzzy matching
This commit is contained in:
@@ -37,7 +37,12 @@ const FUZZY_UNMATCHED_PENALTY = -1;
|
||||
const FUZZY_RECURSION_LIMIT = 10;
|
||||
const FUZZY_TRANSPOSE_PENALTY = -20;
|
||||
const FUZZY_EXACT_SUBSTRING_BONUS = 100;
|
||||
const FUZZY_AND_AMPERSAND_PENALTY = -20;
|
||||
// Per swapped "&" <-> "and" token. Each swap lengthens the matched run by two
|
||||
// characters, worth up to 2*FUZZY_SEQUENTIAL_BONUS to the longer "and" spelling;
|
||||
// penalize each swap by more than that so the literal spelling the user typed always
|
||||
// ranks above the substituted one. (This lowers rank, not visibility — the
|
||||
// substituted match still appears in the results.)
|
||||
const FUZZY_AND_AMPERSAND_PENALTY = -(2 * FUZZY_SEQUENTIAL_BONUS + 20); // -50
|
||||
|
||||
// Options that turn on extra, penalized match passes. Off by default so the base
|
||||
// matcher stays exact-only.
|
||||
@@ -103,17 +108,25 @@ export function fuzzyMatch(query: string, target: string): FuzzyMatchResult | nu
|
||||
return { score, positions: matches };
|
||||
}
|
||||
|
||||
// An alternative spelling of the query plus the number of tokens swapped to
|
||||
// produce it, so the penalty can scale with how far it strayed.
|
||||
interface FuzzyVariant {
|
||||
text: string;
|
||||
swaps: number;
|
||||
}
|
||||
|
||||
// Alternative spellings of `query` with the word "and" and the symbol "&" swapped
|
||||
// for each other, excluding `query` itself. `\band\b` only rewrites a standalone
|
||||
// "and", so "brand" and "island" are left alone. These are the candidates the
|
||||
// matcher tries (penalized) so "... and ..." can still hit a "... & ..." target
|
||||
// and vice versa, while the exact spelling ranks first.
|
||||
function andAmpersandVariants(query: string): string[] {
|
||||
const out: string[] = [];
|
||||
const amp = query.replace(/\band\b/gi, "&");
|
||||
if (amp !== query) out.push(amp);
|
||||
const and = query.replace(/&/g, "and");
|
||||
if (and !== query) out.push(and);
|
||||
// matcher tries (penalized per swap) so "... and ..." can still hit a "... & ..."
|
||||
// target and vice versa, while the exact spelling ranks first.
|
||||
function andAmpersandVariants(query: string): FuzzyVariant[] {
|
||||
const out: FuzzyVariant[] = [];
|
||||
let ands = 0;
|
||||
const amp = query.replace(/\band\b/gi, () => { ands++; return "&"; });
|
||||
if (ands > 0) out.push({ text: amp, swaps: ands });
|
||||
const amps = (query.match(/&/g) || []).length;
|
||||
if (amps > 0) out.push({ text: query.replace(/&/g, "and"), swaps: amps });
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -133,9 +146,9 @@ export function fuzzyMatchTypoTolerant(query: string, target: string, opts?: Fuz
|
||||
}
|
||||
if (opts?.andAmpersand) {
|
||||
for (const variant of andAmpersandVariants(query)) {
|
||||
const m = fuzzyMatch(variant, target);
|
||||
const m = fuzzyMatch(variant.text, target);
|
||||
if (!m) continue;
|
||||
const score = m.score + FUZZY_AND_AMPERSAND_PENALTY;
|
||||
const score = m.score + variant.swaps * FUZZY_AND_AMPERSAND_PENALTY;
|
||||
if (!best || score > best.score) best = { score, positions: m.positions };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user