【问题标题】:Match the nth longest possible string in Perl匹配 Perl 中第 n 个最长的字符串
【发布时间】:2014-07-04 19:16:07
【问题描述】:

Perl 正则表达式的模式匹配量词是“贪婪的”(它们匹配可能的最长字符串)。为了强制匹配“不贪婪”,一个?可以附加到模式量词(*,+)。

这是一个例子:

#!/usr/bin/perl

$string="111s11111s";

#-- greedy match
$string =~ /^(.*)s/;
print "$1\n"; # prints 111s11111

#-- ungreedy match
$string =~ /^(.*?)s/;
print "$1\n"; # prints 111

但是如何在 Perl 中找到第二个、第三个和 .. 可能的字符串匹配?做一个简单的例子——如果需要更好的例子。

【问题讨论】:

  • 不好的例子。这是仅有的两个可能的匹配项!
  • @ikegami 这正是我说“做一个简单的例子——如果需要更好的例子。”。

标签: regex string perl pattern-matching


【解决方案1】:

使用conditional expressioncode expressionbacktracking control verbs

my $skips = 1;
$string =~ /^(.*)s(?(?{$skips-- > 0})(*FAIL))/;

以上将使用贪心匹配,但会导致最大匹配故意失败。如果您想要第三大,您可以将跳过的数量设置为 2。

如下所示:

#!/usr/bin/perl
use strict;
use warnings;

my $string = "111s11111s11111s";

$string =~ /^(.*)s/;
print "Greedy match     - $1\n";

$string =~ /^(.*?)s/;
print "Ungreedy match   - $1\n";

my $skips = 1;
$string =~ /^(.*)s(?(?{$skips-- > 0})(*FAIL))/;
print "2nd Greedy match - $1\n";

输出:

Greedy match     - 111s11111s11111
Ungreedy match   - 111
2nd Greedy match - 111s11111

在使用此类高级功能时,重要的是要全面了解正则表达式以预测结果。这种特殊情况有效,因为正则表达式在一端固定为^。这意味着我们知道每个后续匹配也比前一个短一个。但是,如果两端都可以移动,我们不一定能预测顺序。

如果是这样,那么你就找到它们,然后对它们进行排序:

use strict;
use warnings;

my $string = "111s11111s";

my @seqs;
$string =~ /^(.*)s(?{push @seqs, $1})(*FAIL)/;

my @sorted = sort {length $b <=> length $a} @seqs;

use Data::Dump;
dd @sorted;

输出:

("111s11111s11111", "111s11111", 111)

注意v5.18 之前的 Perl 版本

Perl v5.18 引入了一个更改,/(?{})/ and /(??{})/ have been heavily reworked,它使词法变量的范围能够在上面使用的代码表达式中正常工作。在此之前,上述代码会导致如下错误,如this subroutine version run under v5.16.2所示:

Variable "$skips" will not stay shared at (re_eval 1) line 1.
Variable "@seqs" will not stay shared at (re_eval 2) line 1.

RE 代码表达式的旧实现的修复方法是使用our 声明变量,并且为了进一步的良好编码实践,在初始化时使用localize 它们。这在modified subroutine version run under v5.16.2 中得到了证明,或者如下所示:

local our @seqs;
$string =~ /^(.*)s(?{push @seqs, $1})(*FAIL)/;

【讨论】:

  • my @seqs; 必须是 local our @seqs; 如果在子目录中使用。
  • @ikegami 我对你的警告做了一些研究,并调整了我的答案。看来他们用 5.18 解决了这个问题,但我能够使用ideone.com 确认您对旧版本是正确的。如果我还遗漏了什么,请告诉我。
  • @AmalMurali 我不同意您的编辑,但不同意足以恢复。是的,这三个实例并不是对代码的严格解释,但在这个系统中允许风格变化。
  • 回复“看来他们用 5.18 解决了这个问题”,哦,太好了!
  • @Miller:实际上,没有。这已经讨论过很多次了(参见123)。使用内联代码格式来强调一直是不受欢迎的。这就是粗体和斜体的用途。
【解决方案2】:

首先获取所有可能的匹配项。

my $string = "111s1111s11111s";
local our @matches;
$string =~ /^(.*)s(?{ push @matches, $1 })(?!)/;

这个发现

111s1111s11111
111s1111
111

然后,只需找出哪个是第二长的,然后过滤掉其他的。

use List::MoreUtils qw( uniq );

my $target_length = ( sort { $b <=> $a } uniq map length, @matches )[1];

@matches = uniq grep { length($_) == $target_length } @matches
   if $target_length;

【讨论】:

  • 我只在乎过使用代码表达式one other time,所以没有太多经验。为什么local our?如果您想在the docs 中描述的回溯的情况下本地化对代码表达式中变量的更改,这是否只是一种习惯?
  • @Miller,因为模式是编译时编译的,所以闭包发生在编译时,所以sub foo { my @matches; ... }在第一次调用后会放在错误的变量中(“不会停留共享”)。
  • 1 的任何贡献将不胜感激。到目前为止,只有一个错误的答案。
猜你喜欢
  • 2016-08-28
  • 1970-01-01
  • 2015-10-25
  • 2016-09-15
  • 2020-03-05
  • 2019-03-17
  • 2012-09-17
  • 2012-12-30
  • 2014-08-04
相关资源
最近更新 更多