Skip to content

Commit

Permalink
feat: fuzzy search based on hamming distance
Browse files Browse the repository at this point in the history
  • Loading branch information
christoph-heinrich committed Sep 23, 2023
1 parent 9ea9432 commit 4e05559
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 6 deletions.
47 changes: 41 additions & 6 deletions scripts/uosc/elements/Menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -645,15 +645,50 @@ function Menu:search_update_items()
self:reset_navigation()
end

local function substring_hamming_distance(text, p, p_n)
local best_score, t, t_n = p_n, utf8_chars(text)
for i = 1, t_n - p_n + 1 do
local score = 0
for j = 1, p_n do
if t[i + j - 1] ~= p[j] then
score = score + 1
end
end
if score < best_score then
best_score = score
end
end
return best_score
end

local function fuzzy_search(items, query)
local q, q_n = utf8_chars(query)

local t, n, threshold = {}, 1, q_n / 3 + 1e-9
for _, item in ipairs(items) do
local title = item.title and item.title:lower()
local hint = item.hint and item.hint:lower()
local td = title and substring_hamming_distance(title, q, q_n) or q_n
local hd = hint and substring_hamming_distance(hint, q, q_n) or q_n
local tcd = title and substring_hamming_distance(table.concat(first_word_chars(title)), q, q_n) or q_n
local hcd = hint and substring_hamming_distance(table.concat(first_word_chars(hint)), q, q_n) or q_n
local distance = math.min(td, hd, tcd, hcd)
if distance <= threshold then
t[n] = { distance, n, item }
n = n + 1
end
end
table.sort(t, function(a, b) return a[1] == b[1] and a[2] < b[2] or a[1] < b[1] end)
for i, e in ipairs(t) do
t[i] = e[3]
end
return t
end

---@param menu MenuStack
function Menu:search_internal(menu)
local query = menu.search.query:lower()
menu.items = query ~= '' and itable_filter(menu.search.source.items, function(item)
local title = item.title and item.title:lower() or ''
local hint = item.hint and item.hint:lower() or ''
return title:find(query, 1, true) or hint:find(query, 1, true) or
(table.concat(first_word_chars(title)) .. table.concat(first_word_chars(hint))):find(query, 1, true)
end) or menu.search.source.items
menu.items = query ~= '' and fuzzy_search(menu.search.source.items, query) or menu.search.source.items
self:search_update_items()
end

Expand Down
9 changes: 9 additions & 0 deletions scripts/uosc/lib/text.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ local function utf8_iter(str)
end
end

function utf8_chars(s)
local t, i = {}, 0
for _, c in utf8_iter(s) do
i = i + 1
t[i] = c
end
return t, i
end

---Extract Unicode code point from utf-8 character at index i in str
---@param str string
---@param i integer
Expand Down

0 comments on commit 4e05559

Please sign in to comment.