You haven't really defined what "close enough" means. Here's a few possible definitions
The first two approaches have more obvious drawbacks.
Contains
- Doesn't allow any way to break ties. In principle, either shortest or longest could break ties -- longest allows you to save the most time on average, and shortest means you're more likely to type out most of a name to disambiguate (and in that way is more intuitive)
Length of LCS
- Longer strings to match against will just, by virtue of having more characters, have longer LCSs
- Doesn't match human perception of similarity in some cases since many letters may be skipped in between
I think Levenshtein distance is the most promising.
It has a simple recursive definition (taken from Rosetta Code)
function Levenshtein_Distance( s1, s2 )
if s1:len() == 0 then return s2:len() end
if s2:len() == 0 then return s1:len() end
if s1:sub( -1, -1 ) == s2:sub( -1, -1 ) then
return Levenshtein_Distance( s1:sub( 1, -2 ), s2:sub( 1, -2 ) )
end
local a = Levenshtein_Distance( s1:sub( 1, -2 ), s2:sub( 1, -2 ) )
local b = Levenshtein_Distance( s1:sub( 1, -1 ), s2:sub( 1, -2 ) )
local c = Levenshtein_Distance( s1:sub( 1, -2 ), s2:sub( 1, -1 ) )
if a > b then return b + 1 end
if a > c then return c + 1 end
return a + 1
end
Unfortunately this is incredibly inefficient because of memoization reasons.
A "dynamic programming" approach could solve this, or we can apply memoization (save previously computed values).
local LevenshteinMemoize = {};
function Levenshtein_Distance( s1, s2 )
if #s1 < #s2 then
return Levenshtein_Distance(s2, s1)
end
LevenshteinMemoize[s1] = LevenshteinMemoize[s1] or {}
if LevenshteinMemoize[s1][s2] then
return LevenshteinMemoize[s1][s2]
end
if s1:len() == 0 then return s2:len() end
if s2:len() == 0 then return s1:len() end
if s1:sub( -1, -1 ) == s2:sub( -1, -1 ) then
return Levenshtein_Distance( s1:sub( 1, -2 ), s2:sub( 1, -2 ) )
end
local a = Levenshtein_Distance( s1:sub( 1, -2 ), s2:sub( 1, -2 ) )
local b = Levenshtein_Distance( s1:sub( 1, -1 ), s2:sub( 1, -2 ) )
local c = Levenshtein_Distance( s1:sub( 1, -2 ), s2:sub( 1, -1 ) )
if a > b then
R = b + 1
elseif a > c then
R = c + 1
else
R = a + 1
end
LevenshteinMemoize[s1][s2] = R
return R
end
Now we can find the string most similar by the smallest Levenshtein_Distance:
best = 1
keyword = "volt"
keyword = keyword:lower()
for i = 2, #list do
if Levenshtein_Distance(list[i]:lower(), keyword) <
Levenshtein_Distance(list[best]:lower(), keyword) then
best = i
end
end
2 comments
So best would be the number that matches in the table ? list[1]
I think you overdid it here, Blue. xD