正则表达式匹配最长的重复子字符串

时间:2012-02-09 18:41:01

标签: regex perl pattern-matching

我正在编写正则表达式来检查是否存在子串,其中包含至少2个彼此相邻的模式重复。我将正则表达式的结果与前一个字符串相匹配 - 如果相等,则存在这样的模式。通过示例更好地说:1010包含模式10,并且连续系列中有2次。另一方面,10210将不具有这种模式,因为那些10不相邻。

更重要的是,我需要找到可能的最长模式,并且它的长度至少为1.我已经编写了表达式来检查它^.*?(.+)(\1).*?$。为了找到最长的模式,我使用了非贪婪的版本来匹配模式之前的东西,然后模式匹配到组1,并再次匹配组1匹配的相同的东西。然后匹配其余的字符串,产生相等的字符串。但是有一个问题是正则表达式在找到第一个模式之后急于返回,并且没有真正考虑到我打算在最短的时间之前和之后制作这些子串(剩下最长的可能)。所以从字符串01011010我得到了正确的匹配,但是存储在组1中的模式只是01,尽管我除了101

因为我相信我不能使模式“更贪婪”或者在“更多非贪婪”之前和之后的垃圾,我只能想出一个想法让正则表达式不那么渴望,但我不确定这是不是可能。

进一步的例子:

56712453289 - no pattern - no match with former string
22010110100 - pattern 101 - match with former string (regex resulted in 22010110100 with 101 in group 1)
5555555 - pattern 555 - match
1919191919 - pattern 1919 - match
191919191919 - pattern 191919 - match
2323191919191919 - pattern 191919 - match

使用当前表达式(使用相同的字符串)我会得到什么:

no pattern - no match
pattern 2 - match
pattern 555 - match
pattern 1919 - match
pattern 191919 - match
pattern 23 - match

5 个答案:

答案 0 :(得分:12)

在Perl中,您可以在(??{ code })的帮助下使用一个表达式执行此操作:

$_ = '01011010';
say /(?=(.+)\1)(?!(??{ '.+?(..{' . length($^N) . ',})\1' }))/;

输出:

101

这里发生的事情是,在匹配连续的一对子串之后,我们确保使用负向前瞻,确认不再有一对跟随它。

为了使较长的对的表达式使用推迟的子表达式构造(??{ code }),它将评估内部的代码(每次)并使用返回的字符串作为表达式。

它构造的子表达式具有.+?(..{N,})\1形式,其中N是第一个捕获组的当前长度(length($^N)$^N包含前一个捕获组的当前值。

因此,完整表达式将具有以下形式:

(?=(.+)\1)(?!.+?(..{N,})\2}))

使用神奇的N(和第二个捕获组不是原始表达式的“真实”/正确捕获组)。


Usage example

use v5.10;

sub longest_rep{
    $_[0] =~ /(?=(.+)\1)(?!(??{ '.+?(..{' . length($^N) . ',})\1' }))/;
}

say longest_rep '01011010';
say longest_rep '010110101000110001';
say longest_rep '2323191919191919';
say longest_rep '22010110100';

输出:

101
10001
191919
101

答案 1 :(得分:1)

可以在单个正则表达式中执行此操作,您只需手动从结果列表中选择最长匹配。

def longestrepeating(strg):
    regex = re.compile(r"(?=(.+)\1)")
    matches = regex.findall(strg)
    if matches:
        return max(matches, key=len)

这给你(因为re.findall()返回匹配的捕获组列表,即使匹配本身是零长度):

>>> longestrepeating("yabyababyab")
'abyab'
>>> longestrepeating("10100101")
'010'
>>> strings = ["56712453289", "22010110100", "5555555", "1919191919", 
               "191919191919", "2323191919191919"]
>>> [longestrepeating(s) for s in strings]
[None, '101', '555', '1919', '191919', '191919']

答案 2 :(得分:0)

正则表达式可以帮助解决这个问题,但我不认为你可以将它作为单个表达式来实现,因为你想要找到最长的成功匹配,而正则表达式只是寻找他们能找到的第一场比赛。可以使用贪婪来调整首先找到哪个匹配(字符串中的早期与后期),但我想不出某种方法可以在之后优先使用更早,更长的子字符串,较短的子字符串,而更喜欢以后更长的子字符串而不是更早的子字符串。

使用正则表达式的一种方法是按递减顺序迭代可能的长度,并在找到指定长度的匹配后立即退出:

my $s = '01011010';
my $one = undef;
for(my $i = int (length($s) / 2); $i > 0; --$i)
{
  if($s =~ m/(.{$i})\1/)
  {
    $one = $1;
    last;
  }
}
# now $one is '101'

答案 3 :(得分:0)

这是一个长期的脚本,可以满足您的要求。它基本上通过你的输入字符串,缩短一个,然后再次通过它。一旦找到所有可能的匹配,它将返回最长的匹配之一。可以调整它以便返回所有最长的匹配,而不仅仅是一个,但我会留给你。

这是非常基本的代码,但希望你能得到它的要点。

use v5.10;
use strict;
use warnings;

while (<DATA>) {
    chomp;
    print "$_ : ";
    my $longest = foo($_);
    if ($longest) {
        say $longest;
    } else {
        say "No matches found";
    }
}

sub foo {
    my $num = shift;
    my @hits;
    for my $i (0 .. length($num)) {
        my $part = substr $num, $i;
        push @hits, $part =~ /(.+)(?=\1)/g;
    }
    my $long = shift @hits;
    for (@hits) {
        if (length($long) < length) {
            $long = $_;
        }
    }
    return $long;
}

__DATA__
56712453289
22010110100
5555555
1919191919
191919191919
2323191919191919

答案 4 :(得分:0)

不确定是否有人想到这个...

my $originalstring="pdxabababqababqh1234112341";

my $max=int(length($originalstring)/2);
my @result;
foreach my $n (reverse(1..$max)) {
    @result=$originalstring=~m/(.{$n})\1/g;
    last if @result;
}

print join(",",@result),"\n";

最长的双倍匹配不能超过原始字符串长度的一半,所以我们从那里算起来。

如果相对于原始字符串的长度怀疑匹配较小,那么这个想法可以颠倒...而不是倒数直到我们找到匹配,我们计算直到没有更多的匹配。然后我们需要备份1并给出结果。我们还需要在正则表达式中的$ n之后加一个逗号。

my $n;
foreach (1..$max) {
    unless (@result=$originalstring=~m/(.{$_,})\1/g) {
        $n=--$_;
        last;
    }
}
@result=$originalstring=~m/(.{$n})\1/g;

print join(",",@result),"\n";