fuxxy serch + rebuild script
This commit is contained in:
parent
6625e5927e
commit
27ce5a4a92
3 changed files with 73 additions and 5 deletions
|
|
@ -134,17 +134,67 @@ func (l *Library) Get(id string) (*MediaItem, string, bool) {
|
|||
return item, item.fullPath, true
|
||||
}
|
||||
|
||||
// Search returns items whose name contains the query (case-insensitive).
|
||||
// Search returns items matching the query, ranked by fuzzy match score.
|
||||
func (l *Library) Search(query string) []MediaItem {
|
||||
q := strings.ToLower(query)
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
|
||||
var results []MediaItem
|
||||
type scored struct {
|
||||
item MediaItem
|
||||
score int
|
||||
}
|
||||
var results []scored
|
||||
for _, item := range l.items {
|
||||
if strings.Contains(strings.ToLower(item.Name), q) {
|
||||
results = append(results, item)
|
||||
name := strings.ToLower(item.Name)
|
||||
if s := fuzzyScore(name, q); s > 0 {
|
||||
results = append(results, scored{item, s})
|
||||
}
|
||||
}
|
||||
return results
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].score > results[j].score
|
||||
})
|
||||
|
||||
items := make([]MediaItem, len(results))
|
||||
for i, r := range results {
|
||||
items[i] = r.item
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// fuzzyScore returns a match score for how well target matches the query.
|
||||
// Returns 0 if no match. Higher is better.
|
||||
// Exact substring match gets a big bonus. Otherwise uses subsequence matching
|
||||
// with bonuses for consecutive chars and word-boundary matches.
|
||||
func fuzzyScore(target, query string) int {
|
||||
// exact substring = best possible match
|
||||
if strings.Contains(target, query) {
|
||||
return 1000 + len(query)*10
|
||||
}
|
||||
|
||||
// subsequence match with scoring
|
||||
qi := 0
|
||||
score := 0
|
||||
consecutive := 0
|
||||
for ti := 0; ti < len(target) && qi < len(query); ti++ {
|
||||
if target[ti] == query[qi] {
|
||||
score += 10
|
||||
consecutive++
|
||||
if consecutive > 1 {
|
||||
score += consecutive * 5 // bonus for runs
|
||||
}
|
||||
// word boundary bonus
|
||||
if ti == 0 || target[ti-1] == ' ' || target[ti-1] == '.' || target[ti-1] == '_' || target[ti-1] == '-' {
|
||||
score += 20
|
||||
}
|
||||
qi++
|
||||
} else {
|
||||
consecutive = 0
|
||||
}
|
||||
}
|
||||
|
||||
if qi < len(query) {
|
||||
return 0 // didn't match all query chars
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue