Perl正则表达式按顺序查找包含关键字的字符串

时间:2016-12-07 18:38:52

标签: regex perl

我正在学习Perl。我想在文本中找到所有顺序keyword1keyword2keyword3中3个关键字的出现次数。 keyword1keyword3是可选的。关键字之间最多可包含6个字。这是Perl中的代码:

#!/usr/bin/perl
$reg="(keyword1)*\W*(?:\w+\W+){0,6}?(keyword2)\W*(?:\w+\W+){0,6}?(keyword3)*";
$content="some words before keyword1 optional word here then keyword2 again optional words then keyword3 others words after.";
while ($content=~m/$reg/g) {
    print "$&\n";
} 

我想只提取子串keyword1 optional word here then keyword2 again optional words then keyword3,但我得到了keyword2。谢谢。

1 个答案:

答案 0 :(得分:2)

首先,"\w"生成字符串w"\W"生成字符串W

$ perl -wE'say "\w\W"'
Unrecognized escape \w passed through at -e line 1.
Unrecognized escape \W passed through at -e line 1.
wW

您需要转义反斜杠("\\W")或使用qr//qr/\W/)。

我很确定该模式还有其他问题。我将从头开始。

假设k1k3都是独立可选的,您需要:

qr/
    (?: \b k1 \W+
        (?: \w+ \W+ ){0,6}?
    )?

    \b k2 \b

    (?: 
        (?: \W+ \w+ ){0,6}?
        \W+ k3 \b
    )?
/x

单词边界(\b)用于确保我们不匹配abck2defabck1 k2 k3def

上述效率低下。

以下面的正则表达式模式为例:

(?: x y )? x z

它可以匹配以下字符串:

xyxz
xz

请注意两者都以x开头?这意味着更好的模式(即执行较少回溯的模式)将是

x (?: y x )? z    

在上面的答案中有几个这种反模式的例子。所以,让我们重构一下。

qr/
    \b
    (?: k1 \W+ (?: \w+ \W+ ){0,6}? \b )?
    k2 \b
    (?: \W+ (?: \w+ \W+ ){0,6}? k3 \b  )?
/x

现在我们有了一些有效的东西。

在上述模式中,请注意第二个\b是多余的。所以,让我们摆脱它。

如果我们在最后添加\b,则第三个和第四个\b会变得多余。

应用这些简化后,我们得到:

qr/
    \b
    (?: k1 \W+ (?: \w+ \W+ ){0,6}? )?
    k2
    (?: \W+ (?: \w+ \W+ ){0,6}? k3 )?
    \b
/x

就个人而言,我非常不喜欢非贪婪修饰符,只不过是优化。此外,使用其中两个通常是一个巨大的红旗,模式中有一个错误。例如,模式可以匹配k1 k1 k2,但这可能不合适。

要消除它们,我们需要确保第一个\w+不匹配k1k2。这可以通过替换

来实现
\b \w+ \b

(?! \b k1 \b ) (?! \b k2 \b ) \b \w+ \b

同样,我们将常见前缀分解为:

\b (?! (?: k2 | k3 ) \b ) \w+ \b

同样,我们需要确保第二个\w+不匹配k2k3

通过这些更改,我们得到:

qr/
    \b
    (?: k1 \W+ (?: (?! (?: k1 | k2 ) \b ) \w+ \W+ ){0,6} )?
    k2
    (?: \W+ (?: (?! (?: k2 | k3 ) \b ) \w+ \W+ ){0,6} k3 )?
    \b
/x

复杂?是。更好的解决方案可能首先将流分解为单词和非单词标记。这样做的好处是我们不再需要担心边界。

my @tokens = split(/(\W+)/, $content, -1);

然后,检查数组的模式。由于正则表达式引擎特别擅长这样做,我们可以按如下方式利用它:

my $tokens =
   join '',
      map {
         ($_ % 2) ? "W"
         : $words[$_] eq "k1" ? 1
         : $words[$_] eq "k2" ? 2
         : $words[$_] eq "k3" ? 3
         : "w"                      # Non-key word
      }
         0..$#tokens;

while ($tokens =~ /(?: 1 W (?: w W ){0,6} )? 2 (?: W (?: w W ){0,6} 3 )?/xg) {
   say join('', @tokens[ $-[0] .. $+[0] - 1 ]);
}

鉴于@tokens将永远是形式字,非单词,单词,非单词等,我们也可以使用以下内容:

my $words =
   join '',
      map {
         ($_ % 2) ? ""              # We just want to look at the words
         : $words[$_] eq "k1" ? 1
         : $words[$_] eq "k2" ? 2
         : $words[$_] eq "k3" ? 3
         : "w"                      # Non-key word
      }
         0..$#tokens;

while ($words =~ /(?: 1 w{0,6} )? 2 (?: w{0,6} 3 )?/xg) {
   say join('', @tokens[ $-[0] * 2 .. ( $+[0] - 1 ) * 2 ]);
}