<?xml version="1.0" encoding="windows-1252"?>
<node id="383423" title="Re: Finding longest palindrome from a string" created="2004-08-16 15:38:04" updated="2005-07-03 18:16:09">
<type id="11">
note</type>
<author id="180961">
Limbic~Region</author>
<data>
<field name="doctext">
[BUU],
&lt;br /&gt;
I was frustrated that my original idea, which was elegant and fast, had a major logic flaw and fixing it made ugly inefficient code.  I came up with the following instead:
&lt;readmore&gt;
&lt;CODE&gt;
sub LR_Palindrome {
    my $forward = shift;
    my $reverse = reverse $forward;

    return $forward if $forward eq $reverse;
    my ($max, $pos, $length, $palindrome, $test) = (0); 

    for $pos ( 0 .. (length $forward) - 1 ) {
        for $length ( $max + 1 .. (length $forward) - $pos ) {
            $test = substr( $forward, $pos, $length );
            if ( index($reverse, $test) != -1 ) {
                $max = length $test;
                $palindrome = $test;
            }
        }
    }
    return $palindrome;
}
&lt;/CODE&gt;
&lt;/readmore&gt;
Of course I wanted to see how it stacked up against all the other solutions, so I created a program to generate strings that contained palindromes as well as added in the examples used elsewhere in the thread.
&lt;readmore&gt;
&lt;code&gt;
#!/usr/bin/perl
use strict;
use warnings;

my @letters = 'a'..'z';
open (PALINDROMES, '&gt;', 'palindromes.dat') or die "Unable to open palindromes.dat for writing : $!";

for ( 0 .. 10 ) {
    my $string;
    $string .= rand() &lt;= .3 ? gen_palindrome() : $letters[ rand @letters ] for 0 .. (rand 25) + 2;
    print PALINDROMES $string, "\n";
}

sub gen_palindrome {
    my $string;
    $string .= $letters[ rand @letters ] for 1 .. (rand 18) + 2;
    return $string . reverse $string;
}
__END__
leostddtsoelirknmxrhwfuzoozufwhrjnwytnybfrrrrfbyntywnfxmqrepfcdojyovxeyaeeayexvoyjodcfperqmx
xrurqyovghhybqvbhvyyvhbvqbyhhgvoyqrrrppmycvoovcympprr
zrrpmmlygeyeqrttinhklxfccfxlkhnittrqeyegylryovaaavduooudvaaavoyttugqcihjrnmnmiywwyimnmnrjhicqgutdijvmpofmppmfopmvjimuccqieaeixkgzlpddplzgkxieaeiqccumkagegqqgegakyuplcymxfrbpdzkzryxlxtmzvvzmtxlxyrzkzdpbrfxm
kreguugerfumcvzzvcmujtfjgwsjgfphkusqlgggglqsukhpfgjswsfmujzzjumfs
dhmopumbwennjaalwwlaajnnewbmupomwbyzrryoynrrokdiidkorrnyoyrrzllssllioyllrsnmffmnsrllvsttfeymdjjxbccbxjjdmxlwaiqjkyvsgzppzgsvykjqiawbgeingpaywctdaadtcwyapgnie
mzvnytslsnqadhcsccschdaqnsxvuhrhcnrrnchrhuvxswetuutewdctvkolvipbelnbbnlebpivlokvt
cwxfymnrrnmyqiu
lamhzzcwlwoowlwczzzqeeavmfqvfsywwysfvqfmvaeequmycckptworpfyheawviuvaupnnpuavuivwaehyfprowt
frfbkjwtdgqqgdtwjkbfrfhiloxilguyqbssmbllbmssbqyucxjfjhjwkciuzjjzuickwjto
cvynpiipnyvcjybdltorcntkhpxpwcrvdjjdvrcwpxphktncrotlirekrmokbqkqaaqkqbkomrkerigxknxhekcdmuecceumdckehxneqzxkqryadbfzxvwwvxzfbdayrqkxzqe
heucfmybrgwtikfussufkitwgrktxt
dsdadadcccasdasd
ababbab
1111111121111111111112111111111111111111111112111111111111211111
ababacababacadacababacadaeadacabaz
sacascascsacascascascadvgkgjsflfjgfk
abcdedcbabcdefgfedcbabcdefghijklmnonmlkjihgfedcbabcdefghijklkjihgfedcbabcdefghijklmnoponmlkjihgfedcba
bbabbbabbb
27103271037013711111111111111111116111111111111111111111166026111111111111111111111111111111111111111111111111111611111111111111111
61111111111111111111111111111111111111111111111111116
abcdefghijklmnopqrstuvwxyz12345678987654321zyxwvutsrqponmlkjihgfedcba
ababcbabcdcbabcdedcbabcdefedcbabcdefgfedcbababcbabcdcbabcdedcbabcdefedcbabcdefgfedcba
edcbabcdefedcbabcde
abcdedcbabcdefgfedcbabcdefghijklmnonmlkjihgfedcbabcdefghijklkjihgfedcbabcdefghijklmnoponmlkjihgfedcba
onmlkjihgfedcbabcdefghijklkjihgfedcbabcdefghijklmno
&lt;/code&gt;
&lt;/readmore&gt;
I then created a benchmark script that would not only yield performance results, but accuracy as well.  You have to look at the *.results files as I got lazy.
&lt;readmore&gt;
&lt;code&gt;
#!/usr/bin/perl
use POSIX qw(ceil);
use Benchmark 'cmpthese';

my %methods = (
    LR         =&gt; \&amp;LR_Palindrome,
    BUU        =&gt; \&amp;BUU_Palindrome,
    CCN1       =&gt; \&amp;CCN1_Palindrome,
    CCN2       =&gt; \&amp;CCN2_Palindrome,
    RW         =&gt; \&amp;RW_Palindrome,
    CLIVE      =&gt; \&amp;cLive_Palindrome,
    MURUGU     =&gt; \&amp;murugu_Palindrome,
    JASPER     =&gt; \&amp;jasper_Palindrome,
    DEIBYZ     =&gt; \&amp;deibyz_Palindrome,
    BGREENLEE  =&gt; \&amp;bgreenlee_Palindrome,
    BUK        =&gt; \&amp;buk_Palindrome,
    FIZBIN     =&gt; \&amp;fizbin_Palindrome,
    ARISTOTLE1 =&gt; \&amp;aristotle1_Palindrome,
    ARISTOTLE2 =&gt; \&amp;aristotle2_Palindrome,
    JDP        =&gt; \&amp;JDP_Palindrome,
    ELGON      =&gt; \&amp;elgon_Palindrome,
    JAPHY1     =&gt; \&amp;japhy1_Palindrome,
    JAPHY2     =&gt; \&amp;japhy2_Palindrome,
    WGD        =&gt; \&amp;WGD_Palindrome,
);

for my $method ( keys %methods ) {
    open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
    open (OUT, '&gt;', "$method.results") or die "Unable to open $method.results for writing : $!";
    while ( &lt;DATA&gt; ) {
        chomp;
        print OUT $methods{$method}-&gt;( $_ ), "\n";
    }
}

cmpthese -10, {
    'L~R' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            LR_Palindrome( $_ );
        }
    },
    'BUU' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            BUU_Palindrome( $_ );
        }
    },
    'CCN1' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            CCN1_Palindrome( $_ );
        }
    },
    'CCN2' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            CCN2_Palindrome( $_ );
        }
    },
    'RW' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            RW_Palindrome( $_ );
        }
    },
    'cLive' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            cLive_Palindrome( $_ );
        }
    },
    'murugu' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            murugu_Palindrome( $_ );
        }
    },
    'jasper' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            jasper_Palindrome( $_ );
        }
    },
    'deibyz' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            deibyz_Palindrome( $_ );
        }
    },
    'bgreenlee' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            bgreenlee_Palindrome( $_ );
        }
    },
    'buk' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            buk_Palindrome( $_ );
        }
    },
    'fizbin' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            fizbin_Palindrome( $_ );
        }
    },
    'aristotle1' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            aristotle1_Palindrome( $_ );
        }
    },
    'aristotle2' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            aristotle2_Palindrome( $_ );
        }
    },
    'JDP' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            JDP_Palindrome( $_ );
        }
    },
    'elgon' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            elgon_Palindrome( $_ );
        }
    },
    'japhy1' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            japhy1_Palindrome( $_ );
        }
    },
    'japhy2' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            japhy2_Palindrome( $_ );
        }
    },
    'WGD' =&gt; sub {
        open (DATA, '&lt;', 'palindromes.dat') or die "Unable to open data file for reading : $!";
        while ( &lt;DATA&gt; ) {
            chomp;
            WGD_Palindrome( $_ );
        }
    },
};


sub LR_Palindrome {
    my $forward = shift;
    my $reverse = reverse $forward;

    return $forward if $forward eq $reverse;
    my ($max, $pos, $length, $palindrome, $test) = (0); 

    for $pos ( 0 .. (length $forward) - 1 ) {
        for $length ( $max + 1 .. (length $forward) - $pos ) {
            $test = substr( $forward, $pos, $length );
            if ( index($reverse, $test) != -1 ) {
                $max = length $test;
                $palindrome = $test;
            }
        }
    }
    return $palindrome;
}

sub BUU_Palindrome {
    my @p;
    my $arg = shift;
    my $p;
    
    while( $arg =~ /((.).?\2)/g )
    {
        my $m = $1;
        while( $arg =~ /((.)$m\2)/ )
        {
            $m = $1;
        }
        if( length($m) &gt; length($p) )
        {
            $p = $m;
        }
    }
    
    return $p;
}

sub CCN1_Palindrome {
    local $_ = shift;
    my @n;
    for (my $i = 0; $i &lt; length; pos = $i++) {
        $n[length $&amp;] = $&amp; if /\G(.+).?(??{reverse  $1})/;
    }
    return @n ? $n[-1] : '';
}

sub CCN2_Palindrome {
    local ($_, $s) = shift;
    for (my $i = 0; $i &lt; length; pos = $i++) {
        $s = $&amp; if /\G(.+).?(??{reverse  $1})/ and length($s) &lt; length($&amp;);
    }
    return $s;
}

sub RW_Palindrome {
    my ($left, $right, $pal, $i)=("", "", "", 1);
    my $test=join " ", @ARGV;
    for (; $i&lt;((length $test)/2)+2; $i++) {
        $left.="(.)";
        $right="(\\$i)".$right;
        if ($test=~/$left.?$right/) {$pal=$&amp;; next}
        return $pal;
    }
}

sub cLive_Palindrome {
    my $rev = reverse $_[0];

    my $len='';
    my $d;
    for (0..length($_[0])-1) {
        my $c= join'',map { substr($rev,$_,1) eq substr($_[0],$_,1) ? 1 : 0 } 0..length($_[0])-1;
        my $match = (sort {length($a)&lt;=&gt;length($b)} $c =~ /(1+)/g)[-1];
        $match&gt;$len and $len=$match and $d=$c;
        $rev = substr($rev,1).substr($rev,0,1);
    }
    $d=~s/(.*)($len).*/substr($_[0],length($1),length($len))/e;
    return $d;
}

sub murugu_Palindrome {
    my $x=shift;
    my $prev=0;
    while ($x=~/(([a-z0-9]+)[a-z0-9]?(??{reverse $2}))/gi) {
        $max=$1 if (length($1)&gt;$prev);
        $prev=length $max;
    }
    $max;
}

sub jasper_Palindrome {
  $_ = pop;
  s/\s//sg;
  do {
   push@a,$1 if /((.*).?(??{reverse$2}))/i;
  } while s/.//;
  (sort{length($b)&lt;=&gt;length$a}@a)[0]
}

sub deibyz_Palindrome {
        my $match;
        while(/.*?(.+)(.?)((??{reverse$1})).*?/g){
                $match = $1.$2.$3 if length($1.$2.$3)&gt;length($match);
        }
        $match;
}

sub bgreenlee_Palindrome {
    my $str = shift;
    my $longest = '';
    while ($str =~ /(?=(.*)(.?)((??{reverse $1})))/g) {
        $longest = "$1$2$3" if length("$1$2$3") &gt; length($longest);
    }
    return $longest;
}

sub buk_Palindrome {
    my $string = shift;
    my @pals;
    while( $string =~ m[(.) (?=( (?:\1) | (?:.\1) ) ) ]gx ){
        my( $left, $right ) = ( $-[ 0 ], $+[ -1 ] );

        while( $left and $right &lt; length( $string )
        and substr( $string, $left, 1 ) eq substr( $string, $right, 1 )
        ) { 
            $left--; $right++ ;
        }
        my $pal = substr( $string, $left, $right - $left );

        if( !@pals or length( $pals[ -1 ] ) &lt; length( $pal ) ) {
            @pals = $pal;
        } 
        else {
            push @pals, $pal unless @pals;
        }
    }
    return wantarray ? $pals[ 0 ] : @pals;
}

sub fizbin_Palindrome {
  return $_[0] unless ($_[0] and length($_[0]) &gt; 1);
  my @string = (300, unpack("U*", $_[0]), 301);
  my $palstart, $palend;
  my ($bestlen, $beststart, $bestend) = (-1,-1,-1);
  for ($palmid = 1; $palmid &lt; $#string; $palmid++)
  {
    if ($string[$palmid] == $string[$palmid+1])
    { # try even-length palindrome
      ($palstart, $palend) = ($palmid, $palmid+1);
      while ($string[$palend+1] == $string[$palstart-1])
      {
        $palend++; $palstart--;
      }
      if ($bestlen &lt; $palend - $palstart)
      {
          ($bestlen, $bestend, $beststart) =
          ($palend - $palstart, $palend, $palstart);
      }
    }
    # try odd-length palindrome
    ($palstart, $palend) = ($palmid, $palmid);
    while ($string[$palend+1] == $string[$palstart-1])
    {
      $palend++; $palstart--;
    }
    if ($bestlen &lt; $palend - $palstart)
    {
      ($bestlen, $bestend, $beststart) =
          ($palend - $palstart, $palend, $palstart);
    }
  }
  pack("U*", @string[$beststart..$bestend]);
}

sub aristotle1_Palindrome {
    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 &lt;= length $palindrome;
            my $offs = $-[0];
            --$offs if $offs &gt; $rotate_count; # compensate for marker
            $palindrome = substr $str, $offs, $len;
        }

        substr $rts, 0, 0, chop $rts;
    }

    return $palindrome;
}

sub JDP_Palindrome {
    my $pal;
    for my $i ( 0 .. length($_) )
    {
        last if defined($pal) &amp;&amp; length($_)-$i &lt; length($pal);
        my $j = rindex $_, substr( $_, $i, 1 );
        while ( $j &gt; $i )
        {
            my $s = substr $_, $i, $j-$i+1;
            if ( $s eq reverse $s ) # it's a palindrome
            {    # but is it the longest yet found?
                $pal = $s unless defined $pal &amp;&amp; length($pal) &gt; length($s);
            }
            $j--;
            $j = rindex $_, substr( $_, $i, 1 ), $j;
        }
    }
    $pal
}

sub elgon_Palindrome {
    my $string = shift;
    my %char_hash = map { $_ =&gt; 1} split //, $string;
    foreach my $key (keys %char_hash) {
            my @appearances;
            for (my $i = 0; $i &lt; length($string); $i++) {
                push (@appearances, $i) if substr($string, $i, 1)  eq $key;
            }
            foreach my $start(@appearances) {
                foreach my $finish(reverse @appearances) {
                    next if $start &gt;= $finish;
                    my $half_length = ceil(($finish - $start + 1) / 2);
                    return substr($string , ($start) , ($finish - $start + 1) ) if substr($string, $start, $half_length) eq reverse substr ($string, ($finish - $half_length + 1), $half_length);
                }
            }
    }
    return "FAILED!";
}

sub japhy1_Palindrome {  # 74
our$P="";pop=~m{(.+).?(??{reverse$1})
(?{length$P&lt;length$&amp;and$P=$&amp;})^}xs;$P
}

sub japhy2_Palindrome {  # 68
our@P="";pop=~m{(.+).?(??{reverse$
1})(?{$P[length$&amp;]=$&amp;})^}xs;$P[-1]
}

sub aristotle2_Palindrome {
    my $str = shift;
    my $rts = reverse $str;

    my $palindrome = '';
    my $minlen     = 3;
    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{$minlen,}/g ) {
            my $offs = $-[0];
            --$offs if $offs &gt; $rotate_count;    # compensate for marker
            $palindrome = substr $str, $offs, $+[0] - $-[0];
            $minlen = 1 + length $palindrome;
        }

        substr $rts, 0, 0, chop $rts;
    }

    return $palindrome;
}

sub WGD_Palindrome {

    my $longest_palindrome = '';

    # look for two occurrences of the same character back to back or with another
    # character in-between them to find palindromes:
    while ($input =~ /((.).?\2)/g)
    {
        my $match_position = pos($input);

        # get the positions of the two matching characters:
        my $left_pos  = $match_position -  length $1;
        my $right_pos = $match_position - 1;

        # now go looking to the left and right of each matching character
        # for more matching characters:
        while (nextCharactersMatch($input, $left_pos, $right_pos))
        {
            $left_pos--;
            $right_pos++;
        }

        # extract the palindrome:
        my $offset = ($right_pos - $left_pos) + 1;
        my $palindrome = substr($input, $left_pos, $offset);

        $longest_palindrome = $palindrome
            if (length $palindrome &gt; length $longest_palindrome);

        # backtrack, to find palindromes within this palindrome:
        pos($input) -= (length($1) - 1);
    }
}




sub nextCharactersMatch {
    my ($input, $left_pos, $right_pos) = @_;

    return 1 if (substr($input, $left_pos - 1, 1)
            eq substr($input, $right_pos + 1, 1));
}
&lt;/code&gt;
&lt;/readmore&gt;
I made no attempt to fix the results, so I gigged any solution that didn't have a proper .results file
&lt;ul&gt;
&lt;li&gt;[bgreenlee] : 0.332/s : accurate&lt;/li&gt;
&lt;li&gt;[Jasper]    : 0.474/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;[japhy]2    : 0.655/s : accurate&lt;/li&gt;
&lt;li&gt;[japhy]1    : 0.658/s : accurate&lt;/li&gt;
&lt;li&gt;[ccn]1      : 0.684/s : accurate&lt;/li&gt;
&lt;li&gt;[ccn]2      : 0.690/s : accurate&lt;/li&gt;
&lt;li&gt;[cLive ;-)] : 2.47/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;[deibyz] : 4.12/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt; 
&lt;li&gt;[murugu] : 4.30/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;[Limbic~Region|L~R]  : 12.3/s : accurate&lt;/li&gt;
&lt;li&gt;[BUU] : 18.3/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;[jdporter]      : 39.6/s : accurate&lt;/li&gt;
&lt;li&gt;[aristotle]1      : 64.6/s : accurate&lt;/li&gt;
&lt;li&gt;[fizbin]      : 75.4/s : accurate&lt;/li&gt;
&lt;li&gt;[aristotle]2      : 93.9/s : accurate&lt;/li&gt;
&lt;li&gt;[Elgon] : 114/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;[BrowserUk] : 124/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;[Random_Walk] : 1117/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt; 
&lt;li&gt;[William G. Davis] : 1404/s : &lt;b&gt;not-accurate&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="pmsig"&gt;&lt;div class="pmsig-180961"&gt;
&lt;p&gt;
Cheers - [Limbic~Region|L~R]
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</field>
<field name="root_node">
382567</field>
<field name="parent_node">
382567</field>
</data>
</node>
