有没有Perl的快捷方式来计算字符串中匹配项的数量?

88

假设我有:

my $string = "one.two.three.four";

我该如何使用上下文(context)来获取匹配模式的次数(3)?是否可以用一行代码完成?

我尝试了以下代码:

my ($number) = scalar($string=~/\./gi);

我原以为在$number周围加上括号,可以强制使用数组语境,通过使用scalar,可以获得计数。然而,我只得到了1

9个回答

135

这将把正则表达式本身放置在标量上下文中,这不是您想要的。相反,将正则表达式放入列表上下文中(以获取匹配数),并将放入标量上下文中。

 my $number = () = $string =~ /\./gi;

4
好的,Perlsecret 提出了 "Saturn" 作为一个备用名称。 :) - oalders
1
有人能给我解释一下这段代码吗?我是 Perl 新手,对上下文还不太熟悉。 - Edward Gargan
第一部分,() = $string =~ /\./gi,使匹配运算符在列表上下文中返回匹配结果。这类似于 my @results = $string =~ /\./gi;。接下来,my $number 部分是一个标量值。将列表上下文的结果分配给标量会返回其长度。这与 my $count = @some_list 相同,它返回数组的长度。下面是我对此行为的另一种方式进行可视化的答案。 - Robert P

40

我认为最清晰的描述方式是避免将其立即转换为标量。首先分配到一个数组中,然后在标量上下文中使用该数组。这基本上就是= () =习惯用语所要做的,但没有(很少使用的)习惯用语:

my $string = "one.two.three.four";
my @count = $string =~ /\./g;
print scalar @count;

15
最直接的方式值得加一,goatse运算符很可怕。 - Matteo Riva
3
@count周围的括号虽然没有必要,但可以保留。 - Matteo Riva

25
此外,还可以参考Perlfaq4:(链接) 如果您想要在字符串中计算某个单一字符(X)的数量,则可以使用 tr/// 函数来实现,如下所示:
$string = "ThisXlineXhasXsomeXx'sXinXit";
$count = ($string =~ tr/X//);
print "There are $count X characters in the string";

如果你只想查找单个字符,那么这种方法是可行的。但是,如果你想在一个更大的字符串中计算多个字符子串的数量,tr/// 是无法胜任的。你需要使用全局模式匹配将 while() 循环包裹起来。例如,我们可以计算负整数的数量:

$string = "-9 55 48 -2 23 -76 4 14 -44";
while ($string =~ /-\d+/g) { $count++ }
print "There are $count negative numbers in the string";

另一种版本在列表环境中使用全局匹配,然后将结果赋值给标量,产生匹配次数的计数。

$count = () = $string =~ /-\d+/g;

9
以下代码是单行代码吗?
print $string =~ s/\./\./g;

8

试试这个:

my $string = "one.two.three.four";
my ($number) = scalar( @{[ $string=~/\./gi ]} );

对我来说,它返回3。通过创建一个数组的引用,正则表达式在列表上下文中被求值,并且@{..}取消引用了数组引用。


5
不需要那些括号。 - Brad Gilbert
1
我必须说,我更喜欢这种方法,而不是goatse。实际上,我几乎比goatse喜欢任何东西。 - Wick

1
我注意到,如果你在正则表达式中使用了OR条件(例如/(K..K)|(V.AK)/gi),那么生成的数组可能会包含未定义的元素,这些元素会计入最终的计数。
例如:
my $seq = "TSYCSKSNKRCRRKYGDDDDWWRSQYTTYCSCYTGKSGKTKGGDSCDAYYEAYGKSGKTKGGRNNR";
my $regex = '(K..K)|(V.AK)';
my $count = () = $seq =~ /$regex/gi;
print "$count\n";

给出一个计数值为6。
我在这篇文章中找到了解决方案 如何从数组中删除所有未定义的元素?
my $seq = "TSYCSKSNKRCRRKYGDDDDWWRSQYTTYCSCYTGKSGKTKGGDSCDAYYEAYGKSGKTKGGRNNR";
my $regex = '(K..K)|(V.AK)';
my @count = $seq =~ /$regex/gi;
@count = grep defined, @count; 
my $count = scalar @count;
print "$count\n";

那么这就给出了正确的答案是三。


-1

Friedo的方法是:$a = () = $b =~ $c

但是,甚至可以进一步简化为只有($a) = $b =~ $c,如下所示:

my ($matchcount) = $text =~ s/$findregex/ /gi;

你可以将这个过程封装成一个函数,例如getMatchCount(),这样就不用担心它会改变传入的字符串。
另一方面,你也可以加入一个交换操作,这可能会增加一些计算量,但不会改变原始字符串。
my ($matchcount) = $text =~ s/($findregex)/$1/gi;

除了这是一种替换而不是匹配:它会破坏原始字符串。这与@Mike 6年前的想法相同。 - fishinear
1
@fishinear:这与Mike非常不同。他能够将其打印出来,但无法将其存储到变量中。这种差异是显著的。 - HoldOffHunger
1
如果您需要非破坏性操作,只需使用 s/(regex)/$1/g 或 /(=regex)//g(如果您喜欢冒险的话)。 - android.weasel
@android.weasel 哦,嘿,好主意!正在更新并加上这个备注。我通常会将这样的东西封装在函数中,这样我自己就不必担心传递参数的可销毁性(不确定哪个更快,因为现在它正在进行交换)。但这是有用的信息,正在添加! - HoldOffHunger

-1

另一种方式,

my $string = "one.two.three.four";
@s = split /\./,$string;
print scalar @s - 1;

-1
my $count = 0;
my $pos = -1;
while (($pos = index($string, $match, $pos+1)) > -1) {
  $count++;
}

经过基准测试,速度相当快


这不是一个模式匹配。 - Jim Balter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接