Skip to content

Instantly share code, notes, and snippets.

@cahna
Last active December 26, 2015 04:08
Show Gist options
  • Save cahna/7090392 to your computer and use it in GitHub Desktop.
Save cahna/7090392 to your computer and use it in GitHub Desktop.
For a given string, find the longest palindrome that is a sub-string of the original string (ignoring whitespace)
#!/usr/bin/env luajit
local string = string
local socket = require 'socket'
--- Returns the character at index i (just some syntactic sugar for str:gsub(i,i))
-- @usage ('foobar'):c(3) == 'o' -- True
-- @string i Index of character within string
-- @returns Character at index i within string
function string:c(i)
return self:sub(i,i)
end
--- Extend string type (test if string is palindrome)
function string:is_palindrome()
return string.reverse(self) == self
end
--- Expands a string to build the largest possible palindrome with its middle
-- character(s) at index i
-- @number i Index within string to start outward palindrome-test expansion from
-- @returns Longest palindrome expandable from position i within the string
function string:expand_palindrome(i)
assert(i>0 and i<self:len(), 'Argument out of bounds')
local a = i-1
local z = i+1
local n = #self
-- Handle even length possibilities
if self:c(a) ~= self:c(z) then
if self:c(a) == self:c(i) then
a = a-1
elseif self:c(i) == self:c(z) then
z = z+1
else
return self:c(i) -- Short circuit
end
end
-- Expand outward from middle of string
while a > 0 and z < n do
if self:c(a) ~= self:c(z) then break end
a = a-1
z = z+1
end
return self:sub(a+1,z-1)
end
--- Finds the longest palindrome that is a substring of the given string
-- (ignoring anything that is not a letter)
-- @string self Given string to search
-- @treturn string The longest palindrome within the string
function string:longest_sub_palindrome()
local str = self:gsub('[^%a]', ''):lower()
local ptr = 1
local max = #str
local longest = ''
-- Quick check if string is a palindrome, itself
if str:is_palindrome() then
return str
end
-- Search for sub-palindromes by expanding left and right from a given character
while ptr < max do
local pal = str:expand_palindrome(ptr)
if pal:len() > longest:len() then
longest = pal
end
-- Stop searching if there arent enough chars left to make a longer palindrome than the current longest
if longest:len() / 2 > max - ptr then
break
end
ptr = ptr + 1
end
return longest
end
local tests = {
"Here's a test that should return 'racecar' if everything goes right.",
"Have you ever heard the saying, 'A man, a plan, a canal: Panama,' while driving a too-hot-to-hoot racecar?",
"Degas, are we not drawn onward, we freer few, drawn onward to new eras aged?",
"abcdefgh ijklmnopqrs tuv w x y z",
"No 'x' in 'Nixon.'"
}
for _,str in ipairs(tests) do
local tstart = socket.gettime()
local longest = str:longest_sub_palindrome()
local tend = socket.gettime()
local time = ('%.3f ms'):format((tend-tstart)*1000)
print(time .. ' - ' .. longest)
end
--[[ Example output:
0.260 ms - racecar
0.284 ms - amanaplanacanalpanama
0.019 ms - degasarewenotdrawnonwardwefreerfewdrawnonwardtonewerasaged
0.010 ms - a
0.006 ms - noxinnixon
]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment