Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses

Re: Finding longest palindrome from a string

by Aristotle (Chancellor)
on Aug 13, 2004 at 14:30 UTC ( #382685=note: print w/replies, xml ) Need Help??

in reply to Finding longest palindrome from a string

The XOR dance, with a rotating mirror copy. This runs in O(n). Err, it actually runs in O(n2) of course. The inner loop just happens to be shrouded: Perl's string XOR is not O(1).

sub aristotle { my $str = shift; my $rts = reverse $str; my $palindrome = ''; for my $rotate_count ( 0 .. length( $str ) - 1 ) { my $mask = $str ^ $rts; # to distinguish adjacent palindromes substr $mask, $rotate_count, 0, "\1"; while( $mask =~ /\0{3,}/g ) { my $len = $+[0] - $-[0]; next if $len <= length $palindrome; my $offs = $-[0]; --$offs if $offs > $rotate_count; # compensate for marker $palindrome = substr $str, $offs, $len; } substr $rts, 0, 0, chop $rts; } return $palindrome; }

If you don't understand what's going on, run this for a visual demonstration:

#!/usr/bin/perl use strict; use warnings; sub hd { join ' ', map sprintf("%02X", ord $_), split //, $_[0] } my $str = "abcbadddd"; my $rts = reverse $str; for my $rotate_count ( 0 .. length( $str ) - 1 ) { my $mask = $str ^ $rts; # turn all non-nulls to 0xFF for demonstration purposes $mask =~ tr/\1-\377/\377/; # to distinguish adjacent palindromes substr $mask, $rotate_count, 0, "\1"; while( $mask =~ /\0{3,}/g ) { my $len = $+[0] - $-[0]; my $offs = $-[0]; --$offs if $offs > $rotate_count; # compensate for marker print substr( $str, $offs, $len ), "\n"; } print "$rotate_count: ", hd( $str ), " ^ ", hd( $rts ), " = ", hd( + $mask ), "\n"; substr $rts, 0, 0, chop $rts; }

Update: changed 0 .. POSIX::ceil( length( $str ) / 2 ) to 0 .. length( $str ) - 1. It was a vestige from an earlier trail of thought that was no longer valid.

Update: since I was asked how this works, I'm adding an explanation here.

It is pretty simple: a XOR b = 0 when a = b. If you XOR two strings with each other, you will get a NULL at all locations with identical characters. Now obviously, if you XOR a string with a mirror copy of itself and get all NULLs, then it's a palindrome, because all characters in the rotated copy were identical with all characters of the original.

That's the gist of it. The particular problem given for this thread is complicated by the fact that we have to look for embedded palindromes, and rotating the string unfortunately displaces the rotated copy of an embedded palindrome. To find all embedded palindromes, the mirror copy must be XORed against the original string at each offset. The code does this by rotating the copy n times for a string of length n.

There is one nasty trap left. If the string consists of two adjacent palindromes, such as abbabbafef. Mirroring that yields fefabbabba. If you rotate this three times to the left, the mirror copy becomes abbabbafef and XORing them yields a string of all NULLs, which would indicate that the palindrome is abbabbafef. Oops. The problem is that we forgot to keep track of where inside the mirror copy its original start and end used to be. Palindromes obviously cannot run across that location. That is what the substr $mask, $rotate_count, 0, "\1"; is about: a non-NULL is added to break a string of NULLs running across that location. Of course, now we have to account for that extra character in offsets in the mask.

And that's it. The bulk of the work happens in a single XOR and a pattern match, and other auxiliary tasks are done using very few builtins. That's where it gets its speed. The bulk of the code is merely simple math.

Makeshifts last the longest.

Replies are listed 'Best First'.
Re^2: Finding longest palindrome from a string
by ccn (Vicar) on Aug 13, 2004 at 15:33 UTC

    It's a perfect idea, but your code doesn't work for 'dsdadadcccasdasd'. It outputs 'ccc' instead of 'dadad'.

      Ouch. Thanks for catching that. Only doing length / 2 iterations was a remnant from a slightly different approach I abandoned half-way in. I even tested the final code on a bunch of different inputs but somehow managed not to run into this issue… ugh.

      Sometimes I wonder if I should add Boneheaded Mistakes R Us to my signature.

      Makeshifts last the longest.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://382685]
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2017-06-28 18:17 GMT
Find Nodes?
    Voting Booth?
    How many monitors do you use while coding?

    Results (642 votes). Check out past polls.