Documentation
¶
Overview ¶
Package fuzzy implements fuzzy matching and sorting.
Sort() and Match() implement fuzzy search, e.g. "of" will match "OmniFocus" and "got" will match "Game of Thrones".
Match() compares a query and a string, while Sort() sorts a type that implements fuzzy.Sortable. Both return Result structs for each compared string.
The algorithm is based on Forrest Smith's reverse engineering of Sublime Text's search: https://blog.forrestthewoods.com/reverse-engineering-sublime-text-s-fuzzy-match-4cffeed33fdb
It additionally strips diacritics from sort keys if the query is ASCII.
Index ¶
- Constants
- type Option
- func AdjacencyBonus(bonus float64) Option
- func CamelBonus(bonus float64) Option
- func LeadingLetterPenalty(penalty float64) Option
- func MaxLeadingLetterPenalty(penalty float64) Option
- func SeparatorBonus(bonus float64) Option
- func StripDiacritics(on bool) Option
- func UnmatchedLetterPenalty(penalty float64) Option
- type Result
- type Sortable
- type Sorter
Examples ¶
Constants ¶
const ( DefaultAdjacencyBonus = 5.0 // Bonus for adjacent matches DefaultSeparatorBonus = 10.0 // Bonus if the match is after a separator DefaultCamelBonus = 10.0 // Bonus if match is uppercase and previous is lower DefaultLeadingLetterPenalty = -3.0 // Penalty applied for every letter in string before first match DefaultMaxLeadingLetterPenalty = -9.0 // Maximum penalty for leading letters DefaultUnmatchedLetterPenalty = -1.0 // Penalty for every letter that doesn't match DefaultStripDiacritics = true // Strip diacritics from sort keys if query is plain ASCII )
Default bonuses and penalties for fuzzy sorting. To customise sorting behaviour, pass corresponding Options to New() or Sorter.Configure().
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Option ¶
Option configures a Sorter. Pass one or more Options to New() or Sorter.Configure(). An Option returns another Option to revert the configuration to the previous state.
func AdjacencyBonus ¶
AdjacencyBonus sets the bonus for adjacent matches.
func CamelBonus ¶
CamelBonus sets the bonus applied if match is uppercase and previous character is lowercase.
func LeadingLetterPenalty ¶
LeadingLetterPenalty sets the penalty applied for every character before the first match.
func MaxLeadingLetterPenalty ¶
MaxLeadingLetterPenalty sets the maximum penalty for character preceding the first match.
func SeparatorBonus ¶
SeparatorBonus sets the bonus for matches directly after a separator.
func StripDiacritics ¶
StripDiacritics sets whether diacritics should be stripped.
func UnmatchedLetterPenalty ¶
UnmatchedLetterPenalty sets the penalty for characters that do not match.
type Result ¶
type Result struct {
// Match is whether or not the string matched the query,
// i.e. if all characters in the query are present,
// in order, in the string.
Match bool
// Query is the query that was matched against.
Query string
// Score is how well the string matched the query.
// Higher is better.
Score float64
// SortKey is the string Query was compared to.
SortKey string
}
Result stores the result of a single fuzzy ranking.
func Match ¶
Match scores str against query using the specified sort options.
WARNING: Match creates a new Sorter for every call. Don't use this on large datasets.
func Sort ¶
Sort sorts data against query using a new default Sorter.
Example ¶
Fuzzy sort players by name.
package main
import (
"fmt"
"strings"
)
// Player is a very simple data model.
type Player struct {
Firstname string
Lastname string
}
// Name returns the full name of the Player.
func (p *Player) Name() string {
return strings.TrimSpace(p.Firstname + " " + p.Lastname)
}
// Team is a collection of Player items. This is where fuzzy.Sortable
// must be implemented to enable fuzzy sorting.
type Team []*Player
// Default sort.Interface methods
func (t Team) Len() int { return len(t) }
func (t Team) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
// Less is used as a tie-breaker when fuzzy match score is the same.
func (t Team) Less(i, j int) bool { return t[i].Name() < t[j].Name() }
// Keywords implements Sortable.
// Comparisons are based on the the full name of the player.
func (t Team) Keywords(i int) string { return t[i].Name() }
// Fuzzy sort players by name.
func main() {
var t = Team{
&Player{"Alisson", "Becker"},
&Player{Firstname: "Adrián"},
&Player{"Andy", "Lonergan"},
&Player{"Caoimhín", "Kelleher"},
&Player{"Virgil", "van Dijk"},
&Player{"Joe", "Gomez"},
&Player{"Andy", "Robertson"},
&Player{"Joel", "Matip"},
&Player{"Ki-Jana", "Hoever"},
&Player{"Trent", "Alexander-Arnold"},
&Player{"Sepp", "van den Berg"},
&Player{"Neco", "Williams"},
&Player{Firstname: "Fabinho"},
&Player{"Georginio", "Wijnaldum"},
&Player{"James", "Milner"},
&Player{"Naby", "Keita"},
&Player{"Jordan", "Henderson"},
&Player{"Alex", "Oxlade-Chamberlain"},
&Player{"Xherdan", "Shaqiri"},
&Player{"Curtis", "Jones"},
&Player{"Harvel", "Elliott"},
&Player{"Roberto", "Firmino"},
&Player{"Sadio", "Mané"},
&Player{"Mohamed", "Salah"},
&Player{"Takumi", "Minamino"},
&Player{"Divock", "Origi"},
}
// Unsorted
fmt.Println(t[0].Name())
// Initials
Sort(t, "taa")
fmt.Println(t[0].Name())
// Initials beat start of string
Sort(t, "al")
fmt.Println(t[0].Name())
// Start of word
Sort(t, "ox")
fmt.Println(t[0].Name())
// Earlier in string = better match
Sort(t, "x")
fmt.Println(t[0].Name())
// Diacritics ignored if query is ASCII
Sort(t, "mane")
fmt.Println(t[0].Name())
// But not if query isn't
Sort(t, "né")
fmt.Println(t[0].Name())
Sort(t, "ne")
fmt.Println(t[0].Name())
}
Output: Alisson Becker Trent Alexander-Arnold Andy Lonergan Alex Oxlade-Chamberlain Xherdan Shaqiri Sadio Mané Sadio Mané Neco Williams
func SortStrings ¶
SortStrings fuzzy-sorts a slice of strings.
Example ¶
Sort a slice of strings by fuzzy match.
squad := []string{
"Alisson Becker",
"Adrián",
"Andy Lonergan",
"Caoimhín Kelleher",
"Virgil van Dijk",
"Joe Gomez",
"Andy Robertson",
"Joel Matip",
"Ki-Jana Hoever",
"Trent Alexander-Arnold",
"Sepp van den Berg",
"Neco Williams",
"Fabinho",
"Georginio Wijnaldum",
"James Milner",
"Naby Keita",
"Jordan Henderson",
"Alex Oxlade-Chamberlain",
"Xherdan Shaqiri",
"Curtis Jones",
"Harvel Elliott",
"Roberto Firmino",
"Sadio Mané",
"Mohamed Salah",
"Takumi Minamino",
"Divock Origi",
}
// Unsorted
fmt.Println(squad[0])
// Initials
SortStrings(squad, "taa")
fmt.Println(squad[0])
// Initials beat start of string
SortStrings(squad, "al")
fmt.Println(squad[0])
// Start of word
SortStrings(squad, "ox")
fmt.Println(squad[0])
// Earlier in string = better match
SortStrings(squad, "x")
fmt.Println(squad[0])
// Diacritics ignored when query is ASCII
SortStrings(squad, "mane")
fmt.Println(squad[0])
// But not if query isn't
SortStrings(squad, "né")
fmt.Println(squad[0])
SortStrings(squad, "ne")
fmt.Println(squad[0])
Output: Alisson Becker Trent Alexander-Arnold Andy Lonergan Alex Oxlade-Chamberlain Xherdan Shaqiri Sadio Mané Sadio Mané Neco Williams
type Sortable ¶
type Sortable interface {
sort.Interface
// Keywords returns the string to compare to the sort query
Keywords(i int) string
}
Sortable makes the implementer fuzzy-sortable. It is a superset of sort.Interface (i.e. your struct must also implement sort.Interface).
type Sorter ¶
type Sorter struct {
Data Sortable // Data to sort
AdjacencyBonus float64 // Bonus for adjacent matches
SeparatorBonus float64 // Bonus if the match is after a separator
CamelBonus float64 // Bonus if match is uppercase and previous is lower
LeadingLetterPenalty float64 // Penalty applied for every letter in string before first match
MaxLeadingLetterPenalty float64 // Maximum penalty for leading letters
UnmatchedLetterPenalty float64 // Penalty for every letter that doesn't match
StripDiacritics bool // Strip diacritics from sort keys if query is plain ASCII
// contains filtered or unexported fields
}
Sorter sorts Data based on the query passsed to Sorter.Sort().