Perl - 2个或更多字符串的最长公共前缀?

时间:2016-11-05 16:54:56

标签: perl

如何创建一个Perl子例程,该子例程将接收一个数组并找到2个或更多元素的最长公共前缀? (字符串)

我有这段代码:

sub longest_common_prefix {
    $prefix = shift;
    for (@_) {
        chop $prefix while (! /^\Q$prefix\E/);
        }
    return $prefix;
}

但只有在您寻找所有字符串的最长公共前缀时,它才有效。

例如,如果我传递一个包含以下字符串的数组:

aaaBGFB
aaaJJJJ
jjfkBBB
aaaHGHG

我希望它返回aaa作为答案。

谢谢!

3 个答案:

答案 0 :(得分:5)

我使用修改后的trie

通常,可以使用以下内容添加到trie:

sub add {
    my $p = \shift;
    my $s = shift;
    $p = \( $$p->{$_} ) for split(//, $s);
    $$p->{''} = 1;
}

但我们需要两次修改:

  • 添加字符串时,必须添加字符串的所有前缀。例如,添加abc也应该将aab添加到特里。
  • 添加到trie时,我们希望返回先前存在的路径部分的长度。

所以我们需要:

sub add {
    my $p = \shift;
    my $s = shift;

    my $cp_len = 0;
    for (split(//, $s)) {
       $p = \( $$p->{$_} );
       ++$cp_len if $$p->{$_}{''};
       $$p->{''} = 1;
    }

    return $cp_len;
}

将此算法(与其优化版本)结合使用算法以查找列表中最长的字符串,并使用算法从列表中删除重复的字符串以获得以下解决方案:

use strict;
use warnings;
use feature qw( say );

sub add {
    my $p = \shift;
    my $s = shift;

    my $cp_len = 0;
    for (split(//, $s)) {
       ++$cp_len if exists($$p->{$_});
       $p = \( $$p->{$_} );
    }

    return $cp_len;
}

my $t;
my $lcp_len = 0;  # lcp = longest common prefix
my %lcps;
while (<>) {
   chomp;
   my $cp_len = add($t, $_)
      or next;

   if ($cp_len >= $lcp_len) {
      if ($cp_len > $lcp_len) {
         $lcp_len = $cp_len;
         %lcps = ();
      }

      $lcps{ substr($_, 0, $cp_len) } = 1;
   }
}

my @lcps = sort keys %lcps;

if (@lcps) {
   say "Longest common prefix(es): @lcps";
} else {
   say "No common prefix";
}

数据:

abc
abc
abcd
abcde
hijklx
hijkly
mnopqx
mnopqy

输出:

Longest common prefix(es): hijkl mnopq

上述时间与输入字符数成正比。

答案 1 :(得分:0)

一种方法是将信息存储在散列中。在此示例中,我将散列键设置为每个前缀的长度,并且值是找到的实际前缀。

请注意,如果存在相同长度的前缀,此方法将覆盖键和值,因此您始终可以获得最长的 last 前缀(sort()处理找到最长的一个)。

正则表达式说“找到字符串中的第一个字符并捕获它,并使用在第二次捕获中找到的字符,并捕获尽可能多的字符”。然后将此字符串join()编入标量并放入哈希值。

use warnings;
use strict;

my %prefixes;

while (<DATA>){
    my $prefix = join '', /^(.)(\1+)/;
    $prefixes{length $prefix} = $prefix; 
}

my $longest = (sort {$b <=> $a} keys %prefixes)[0];
print "$prefixes{$longest}\n";

__DATA__
aaBGFB
aaaJJJJ
jjfkBBB
aaaHGHG

输出:

aaa

答案 2 :(得分:0)

您可以保留由第一个字符键入的单词数组的哈希值。根据定义,如果您有以相同字母开头的单词,则这些单词至少共享该单个字母的一个字符共同前缀。然后通过按字符逐步遍历来减少到单个最长前缀:

use strict; use warnings;
sub lcp {
    (join("\0", @_) =~ /^ ([^\0]*) [^\0]* (?:\0 \1 [^\0]*)* $/sx)[0];
}

my %HoA;
my $longest='';

while (my $line=<DATA>){
    $line =~ s/^\s+|\s+$//g ;
    push @{ $HoA{substr $line, 0, 1} }, $line if $line=~/^[a-zA-Z]/;
}

for my $key ( sort (keys %HoA )) {
    if (scalar @{ $HoA{$key} } > 1){
        my $lon=lcp(@{ $HoA{$key} });
        my $s = join ', ', map { qq/"$_"/ } @{ $HoA{$key} };
        print "lcp: \"$lon\" for ($s)\n";
        if (length($lon) > length($longest)) {
            $longest=$lon;
        }            
    }
    else{
        print "$key: no common prefix\n";
    }
}
print "\nlongest common prefix is \"$longest\"\n";

__DATA__
aardvark
aaaBGFB
aaaJJJJ
jjfkBBB
aaaHGHG
interspecies
interstellar
interstate   

打印:

lcp: "aa" for ("aardvark", "aaaBGFB", "aaaJJJJ", "aaaHGHG")
lcp: "inters" for ("interspecies", "interstellar", "interstate")
j: no common prefix

longest common prefix is "inters"