VH-NZZ
VH-NZZ

Reputation: 5438

Haskell (formidably long) palindrome check

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

Answers (3)

danbst
danbst

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

Ganesh Sittampalam
Ganesh Sittampalam

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

mhwombat
mhwombat

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

Related Questions