103 lines
2.3 KiB
Go
103 lines
2.3 KiB
Go
// Package matching provides matching features that find appropriate strings
|
|
// by using a passed input string.
|
|
package matching
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/ktr0731/go-fuzzyfinder/scoring"
|
|
)
|
|
|
|
// Matched represents a result of FindAll.
|
|
type Matched struct {
|
|
// Idx is the index of an item of the original slice which was used to
|
|
// search matched strings.
|
|
Idx int
|
|
// Pos is the range of matched position.
|
|
// [2]int represents an open interval of a position.
|
|
Pos [2]int
|
|
// score is the value that indicates how it similar to the input string.
|
|
// The bigger score, the more similar it is.
|
|
score int
|
|
}
|
|
|
|
// Option represents available matching options.
|
|
type Option func(*opt)
|
|
|
|
type Mode int
|
|
|
|
const (
|
|
ModeSmart Mode = iota
|
|
ModeCaseSensitive
|
|
ModeCaseInsensitive
|
|
)
|
|
|
|
// opt represents available options and its default values.
|
|
type opt struct {
|
|
mode Mode
|
|
}
|
|
|
|
// WithMode specifies a matching mode. The default mode is ModeSmart.
|
|
func WithMode(m Mode) Option {
|
|
return func(o *opt) {
|
|
o.mode = m
|
|
}
|
|
}
|
|
|
|
// FindAll tries to find out sub-strings from slice that match the passed argument in.
|
|
// The returned slice is sorted by similarity scores in descending order.
|
|
func FindAll(in string, slice []string, opts ...Option) []Matched {
|
|
var opt opt
|
|
for _, o := range opts {
|
|
o(&opt)
|
|
}
|
|
m := match(in, slice, opt)
|
|
sort.Slice(m, func(i, j int) bool {
|
|
if m[i].score == m[j].score {
|
|
return m[i].Idx > m[j].Idx
|
|
}
|
|
return m[i].score > m[j].score
|
|
})
|
|
return m
|
|
}
|
|
|
|
// match iterates each string of slice for check whether it is matched to the input string.
|
|
func match(input string, slice []string, opt opt) (res []Matched) {
|
|
if opt.mode == ModeSmart {
|
|
// Find an upper-case rune
|
|
n := strings.IndexFunc(input, unicode.IsUpper)
|
|
if n == -1 {
|
|
opt.mode = ModeCaseInsensitive
|
|
input = strings.ToLower(input)
|
|
} else {
|
|
opt.mode = ModeCaseSensitive
|
|
}
|
|
}
|
|
|
|
in := []rune(input)
|
|
for idxOfSlice, s := range slice {
|
|
var idx int
|
|
if opt.mode == ModeCaseInsensitive {
|
|
s = strings.ToLower(s)
|
|
}
|
|
LINE_MATCHING:
|
|
for _, r := range s {
|
|
if r == in[idx] {
|
|
idx++
|
|
if idx == len(in) {
|
|
score, pos := scoring.Calculate(s, input)
|
|
res = append(res, Matched{
|
|
Idx: idxOfSlice,
|
|
Pos: pos,
|
|
score: score,
|
|
})
|
|
break LINE_MATCHING
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res
|
|
}
|