Skip to content

Commit

Permalink
perf: optimized search
Browse files Browse the repository at this point in the history
  • Loading branch information
christoph-heinrich committed Sep 26, 2023
1 parent 6b59f01 commit bb3a79a
Showing 1 changed file with 51 additions and 20 deletions.
71 changes: 51 additions & 20 deletions scripts/uosc/elements/Menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,6 @@ local function levenshtein_distance(s1_chars, from, to, s2_chars, s2_char_count,
local rows = to - from + 2
if cols == 1 then return rows - 1 end
if rows == 1 then return cols - 1 end
for r = 1, rows do if not matrix[r] then matrix[r] = { r - 1 } else matrix[r][1] = r - 1 end end
for c = 1, cols do matrix[1][c] = c - 1 end
for r = 2, rows do
for c = 2, cols do
if s1_chars[from + r - 2] == s2_chars[c - 1] then
Expand All @@ -674,13 +672,14 @@ local function levenshtein_distance(s1_chars, from, to, s2_chars, s2_char_count,
end

---Calculates the lowest levenshtein distance of any substring of length pattern_char_count in text.
---@param text string
---@param text_chars string[]
---@param text_char_count integer
---@param pattern_chars string[]
---@param pattern_char_count integer
---@param matrix integer[][]
---@return integer
local function substring_levenshtein_distance(text, pattern_chars, pattern_char_count, matrix)
local best_score, text_chars, text_char_count = pattern_char_count, utf8_chars(text)
local function substring_levenshtein_distance(text_chars, text_char_count, pattern_chars, pattern_char_count, matrix)
local best_score = pattern_char_count
for i = -pattern_char_count + 2, text_char_count do
local from, to = math.max(1, i), math.min(i + pattern_char_count - 1, text_char_count)
local score = levenshtein_distance(text_chars, from, to, pattern_chars, pattern_char_count, matrix)
Expand All @@ -692,22 +691,22 @@ local function substring_levenshtein_distance(text, pattern_chars, pattern_char_
end

---Sort and filter items based on the hamming distance
---@param items MenuStackItem[]
---@param source {scroll_y: number; selected_index?: integer; items?: MenuDataItem[], prepared: { matrix: integer [][]; text: { tc: string[]; tcn: integer; hc: string[]; hcn: integer; twc: string[]; twcn: integer; hwc: string[]; hwcn: integer;}[] }}
---@param query string
---@return MenuStackItem[]
local function fuzzy_search(items, query)
local t, n, matrix, query_chars, query_char_count = {}, 1, {}, utf8_chars(query)
for _, item in ipairs(items) do
local title = item.title and item.title:lower()
local hint = item.hint and item.hint:lower()
local td, tcd, hd, hcd = INFINITY, INFINITY, INFINITY, INFINITY
if title then
td = substring_levenshtein_distance(title, query_chars, query_char_count, matrix)
tcd = substring_levenshtein_distance(table.concat(initials(title)), query_chars, query_char_count, matrix)
local function fuzzy_search(source, query)
local t, n, query_chars, query_char_count = {}, 1, utf8_chars(query)
local matrix, text = source.prepared.matrix, source.prepared.text
for i = 1, query_char_count + 1 do matrix[1][i] = i - 1 end
for i, item in ipairs(source.items) do
local p, td, tcd, hd, hcd = text[i], INFINITY, INFINITY, INFINITY, INFINITY
if p.tc then
td = substring_levenshtein_distance(p.tc, p.tcn, query_chars, query_char_count, matrix)
tcd = substring_levenshtein_distance(p.twc, p.twcn, query_chars, query_char_count, matrix)
end
if hint then
hd = substring_levenshtein_distance(hint, query_chars, query_char_count, matrix)
hcd = substring_levenshtein_distance(table.concat(initials(hint)), query_chars, query_char_count, matrix)
if p.hc then
hd = substring_levenshtein_distance(p.hc, p.hcn, query_chars, query_char_count, matrix)
hcd = substring_levenshtein_distance(p.hwc, p.hwcn, query_chars, query_char_count, matrix)
end
local distance = math.min(td, hd, tcd, hcd)
if distance ~= INFINITY then
Expand All @@ -720,10 +719,41 @@ local function fuzzy_search(items, query)
return t
end

---@param items MenuStackItem[]
---@return { matrix: integer [][]; text: { tc: string[]; tcn: integer; hc: string[]; hcn: integer; twc: string[]; twcn: integer; hwc: string[]; hwcn: integer;}[] }
function search_prepare_search(items)
local t, n_max = {}, 0
for i, item in ipairs(items) do
local title = item.title and item.title:lower()
local hint = item.hint and item.hint:lower()
local tc, tcn, hc, hcn, twc, twcn, hwc, hwcn = nil, nil, nil, nil, nil, nil, nil, nil
if title then
local title_initials = table.concat(initials(title))
tc, tcn = utf8_chars(title)
twc, twcn = utf8_chars(title_initials)
end
if hint then
local hint_initials = table.concat(initials(hint))
hc, hcn = utf8_chars(hint)
hwc, hwcn = utf8_chars(hint_initials)
end
local n = math.max(tcn or 0, hcn or 0)
if n > n_max then
n_max = n
end
t[i] = {
tc = tc, tcn = tcn, hc = hc, hcn = hcn, twc = twc, twcn = twcn, hwc = hwc, hwcn = hwcn,
}
end
local matrix = {}
for i = 1, n_max + 1 do matrix[i] = { i - 1 } end
return { text = t, matrix = matrix }
end

---@param menu MenuStack
function Menu:search_internal(menu)
local query = menu.search.query:lower()
menu.items = query ~= '' and fuzzy_search(menu.search.source.items, query) or menu.search.source.items
menu.items = query ~= '' and fuzzy_search(menu.search.source, query) or menu.search.source.items
self:search_update_items()
end

Expand Down Expand Up @@ -822,7 +852,8 @@ function Menu:search_start()
query = '', timeout = timeout, width = menu.width, top = menu.top,
source = {
scroll_y = menu.scroll_y, selected_index = menu.selected_index,
items = not menu.on_search and menu.items or nil
items = not menu.on_search and menu.items or nil,
prepared = not menu.on_search and search_prepare_search(menu.items) or nil,
},
}
self:search_ensure_key_bindings()
Expand Down

0 comments on commit bb3a79a

Please sign in to comment.