fix ampersand ordering bug for fuzzy matching
This commit is contained in:
@@ -57,7 +57,12 @@ const (
|
||||
fuzzyRecursionLimit = 10
|
||||
fuzzyTransposePenalty = -20
|
||||
fuzzyExactSubstrBonus = 100
|
||||
fuzzyAndAmpersandPen = -20
|
||||
// Per swapped "&"<->"and" token. Each swap lengthens the matched run by two
|
||||
// characters, worth up to 2*fuzzySequentialBonus 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.)
|
||||
fuzzyAndAmpersandPen = -(2*fuzzySequentialBonus + 20) // -50
|
||||
)
|
||||
|
||||
// FuzzyOptions turns on extra, penalized match passes. The zero value is exact-
|
||||
@@ -176,10 +181,12 @@ func fuzzyByteAt(s string, i int) byte {
|
||||
}
|
||||
|
||||
// fuzzyReplaceAndWord replaces every standalone "and" (case-insensitive) in s
|
||||
// with repl, leaving substrings like "brand" or "island" untouched. "and", "&"
|
||||
// and the ASCII separators are all single-byte, so a byte scan suffices.
|
||||
func fuzzyReplaceAndWord(s, repl string) string {
|
||||
// with repl, leaving substrings like "brand" or "island" untouched, and reports
|
||||
// how many it replaced. "and", "&" and the ASCII separators are all single-byte,
|
||||
// so a byte scan suffices.
|
||||
func fuzzyReplaceAndWord(s, repl string) (string, int) {
|
||||
var b strings.Builder
|
||||
n := 0
|
||||
for i := 0; i < len(s); {
|
||||
isAnd := i+3 <= len(s) &&
|
||||
(s[i] == 'a' || s[i] == 'A') &&
|
||||
@@ -187,26 +194,35 @@ func fuzzyReplaceAndWord(s, repl string) string {
|
||||
(s[i+2] == 'd' || s[i+2] == 'D')
|
||||
if isAnd && !fuzzyIsWordByte(fuzzyByteAt(s, i-1)) && !fuzzyIsWordByte(fuzzyByteAt(s, i+3)) {
|
||||
b.WriteString(repl)
|
||||
n++
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
b.WriteByte(s[i])
|
||||
i++
|
||||
}
|
||||
return b.String()
|
||||
return b.String(), n
|
||||
}
|
||||
|
||||
// fuzzyVariant is 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.
|
||||
type fuzzyVariant struct {
|
||||
text string
|
||||
swaps int
|
||||
}
|
||||
|
||||
// fuzzyAndAmpersandVariants returns alternative spellings of query with the word
|
||||
// "and" and the symbol "&" swapped for each other, excluding query itself. These
|
||||
// are the candidates the matcher tries (penalized) so "... and ..." can still hit
|
||||
// a "... & ..." target and vice versa, while the exact spelling ranks first.
|
||||
func fuzzyAndAmpersandVariants(query string) []string {
|
||||
var out []string
|
||||
if amp := fuzzyReplaceAndWord(query, "&"); amp != query {
|
||||
out = append(out, amp)
|
||||
// are the candidates the matcher tries (penalized per swap) so "... and ..." can
|
||||
// still hit a "... & ..." target and vice versa, while the exact spelling ranks
|
||||
// first.
|
||||
func fuzzyAndAmpersandVariants(query string) []fuzzyVariant {
|
||||
var out []fuzzyVariant
|
||||
if amp, n := fuzzyReplaceAndWord(query, "&"); n > 0 {
|
||||
out = append(out, fuzzyVariant{amp, n})
|
||||
}
|
||||
if and := strings.ReplaceAll(query, "&", "and"); and != query {
|
||||
out = append(out, and)
|
||||
if n := strings.Count(query, "&"); n > 0 {
|
||||
out = append(out, fuzzyVariant{strings.ReplaceAll(query, "&", "and"), n})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -232,11 +248,11 @@ func FuzzyMatchTypoTolerant(query, target string, opts ...FuzzyOptions) *FuzzyMa
|
||||
}
|
||||
if fuzzyOpt(opts).AndAmpersand {
|
||||
for _, variant := range fuzzyAndAmpersandVariants(query) {
|
||||
m := FuzzyMatchOne(variant, target)
|
||||
m := FuzzyMatchOne(variant.text, target)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
score := m.Score + fuzzyAndAmpersandPen
|
||||
score := m.Score + variant.swaps*fuzzyAndAmpersandPen
|
||||
if best == nil || score > best.Score {
|
||||
best = &FuzzyMatchResult{Score: score, Positions: m.Positions}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user