我正在学习Perl。我想在文本中找到所有顺序:keyword1
,keyword2
和keyword3
中3个关键字的出现次数。 keyword1
和keyword3
是可选的。关键字之间最多可包含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
。谢谢。
答案 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/
)。
我很确定该模式还有其他问题。我将从头开始。
假设k1
和k3
都是独立可选的,您需要:
qr/
(?: \b k1 \W+
(?: \w+ \W+ ){0,6}?
)?
\b k2 \b
(?:
(?: \W+ \w+ ){0,6}?
\W+ k3 \b
)?
/x
单词边界(\b
)用于确保我们不匹配abck2def
或abck1 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+
不匹配k1
或k2
。这可以通过替换
\b \w+ \b
与
(?! \b k1 \b ) (?! \b k2 \b ) \b \w+ \b
同样,我们将常见前缀分解为:
\b (?! (?: k2 | k3 ) \b ) \w+ \b
同样,我们需要确保第二个\w+
不匹配k2
或k3
。
通过这些更改,我们得到:
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 ]);
}