Reputation: 5438
I'm working my way up the infamous H-99 problems and I'm playing around problem #6 (find out whether a list is a palindrome). I understand most solutions will work on reasonably well on reasonably short lists. Now how would I write a function to test if a very long list is a palindrome, e.g.,
let ll = [1..10000000] ++ [10000000, 10000000-1..1] ++ [7]
I (naively perhaps) tried to test it as such:
isPalindrome [] = True
isPalindrome [_] = True
isPalindrome [x,y] = x==y
isPalindrome (x:xs) = isPalindrome [x,last xs] && (isPalindrome $ init xs)
where I'm assuming that if isPalindrome [x,last xs]
evaluates to False
, the expensive right hand side of &&
will not be evaluated.
I profiled the above and here's what it yielded:
Mon Jun 30 18:33 2014 Time and Allocation Profiling Report (Final)
h99 +RTS -p -RTS
total time = 1.29 secs (1292 ticks @ 1000 us, 1 processor)
total alloc = 2,720,050,200 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
main Main 95.6 100.0
isPalindrome Main 4.4 0.0
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 43 0 0.0 0.0 100.0 100.0
CAF Main 85 0 0.0 0.0 100.0 100.0
main Main 86 1 95.6 100.0 100.0 100.0
isPalindrome Main 87 2 4.4 0.0 4.4 0.0
CAF GHC.Conc.Signal 84 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding 77 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 75 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Handle.FD 68 0 0.0 0.0 0.0 0.0
CAF GHC.Show 63 0 0.0 0.0 0.0 0.0
from which I infer that the problem is with last xs
which could require the whole xs
to be computed. If so, is there a workaround? Or is just the implementation of [a..b]
that is
greedy?
Thanks.
Upvotes: 1
Views: 1435
Reputation: 3623
A bunch of interesting palindrome functions were suggested on this reddit thread:
Naive
palindrome list = reverse list == list
Pointfree
palindrome = liftM2 (==) reverse id
Applicative
palindrome = (==) <$> reverse <*> id
Monadic
palindrome = reverse >>= (==)
Upvotes: 1
Reputation: 29100
As you surmise, last xs
is the problem - it'll take linear time in the length of the list, and force the entire list to be generated immediately ([a..b]
is lazy, as with Haskell lists in general). So your isPalindrome
function will be quadratic time in total.
For a naive implementation, I would start with xs == reverse xs
, which has the right asymptotic behaviour (linear) but a relatively high constant factor, and will end up having two entire copies of the list in memory at the point that reverse
gets to the end of the list and starts producing results.
You can probably improve on that approach somewhat by looking at the length
and then splitting the list at the halfway point, or maybe doing something cleverer in a single pass.
However I think for substantially better behaviour, you'd need to switch data structures - I'd look at something like Data.Deque
or Data.Sequence
.
Upvotes: 5
Reputation: 8136
I think it is the last xs
, as you suspect. Suggestion: before you do any recursion, split the string at the midpoint*, and reverse the second half. Then you're comparing the first character of both strings each time you recurse. *If the strings of odd length, both strings should include the middle character.
Upvotes: 1