繁体   English   中英

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

[英]Regex to match the longest repeating substring

我正在编写正则表达式来检查是否存在子串,其中包含至少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

在Perl中,您可以在(??{ code })帮助下使用一个表达式来完成它:

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

输出:

101

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

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

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

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

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

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


用法示例

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

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

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']

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

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

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'

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

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

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

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

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";

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM