You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

96 lines
2.4 KiB

3 years ago
let SCORE_MIN = -Infinity
let SCORE_MAX = Infinity
let SCORE_GAP_LEADING = -0.005
let SCORE_GAP_TRAILING = -0.005
let SCORE_GAP_INNER = -0.01
let SCORE_MATCH_CONSECUTIVE = 1.0
let SCORE_MATCH_SLASH = 0.9
let SCORE_MATCH_WORD = 0.8
let SCORE_MATCH_DOT = 0.6
def fzy arr, query, keyname="name"
let needle = query.trim!.toLowerCase!
return [] unless arr.length > 0
let scored = []
let M = new Array(100_000)
let D = new Array(100_000)
let B = new Array(100_000)
for obj in arr
continue unless obj.hasOwnProperty keyname
let haystack = obj[keyname].trim!.toLowerCase!
continue unless has_match needle, haystack
obj.fzy_score = score(needle, haystack, M, D, B)
sorted_insert obj, scored
scored
def score needle, haystack, M, D, match_bonus
let n = needle.length
let m = haystack.length
if n < 1 or m < 1
return SCORE_MIN
if n === m
return SCORE_MAX
if m > 1024
return SCORE_MIN
compute needle, haystack, M, D, match_bonus
M[(n - 1)*m + (m - 1)]
def compute needle, haystack, M, D, match_bonus
let n = needle.length
let m = haystack.length
precompute_bonus haystack, match_bonus
for i in [0 .. n - 1]
let prev_score = SCORE_MIN
let gap_score = i === n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER
for j in [0 .. m - 1]
let ij = i*m + j
let pij = (i - 1)*m + (j - 1)
if needle[i] === haystack[j]
let score = SCORE_MIN
if i === 0
score = (j * SCORE_GAP_LEADING) + match_bonus[j]
elif j > 0
score = Math.max(M[pij] + match_bonus[j], D[pij] + SCORE_MATCH_CONSECUTIVE)
D[ij] = score
M[ij] = prev_score = Math.max(score, prev_score + gap_score)
else
D[ij] = SCORE_MIN
M[ij] = prev_score = prev_score + gap_score
def precompute_bonus haystack, match_bonus
let m = haystack.length
let last_ch = '/'
for i in [0 .. m - 1]
let ch = haystack[i]
if last_ch === '/'
match_bonus[i] = SCORE_MATCH_SLASH
elif last_ch === '-' || last_ch === '_' || last_ch === ' '
match_bonus[i] = SCORE_MATCH_WORD
elif last_ch === '.'
match_bonus[i] = SCORE_MATCH_DOT
else
match_bonus[i] = 0
last_ch = ch
def has_match needle, haystack
let i = 0
let n = -1
let letter
while letter = needle[i++]
if (n = haystack.indexOf(letter, n + 1)) === -1
return no
return yes
def sorted_insert elem, arr
let low = 0
let high = arr.length
while low < high
let mid = (low + high) >>> 1
if elem.fzy_score > arr[mid].fzy_score
high = mid
else
low = mid + 1
arr.splice(low, 0, elem)
export default fzy