Last active
December 26, 2015 04:08
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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