在Perl中如何计算字符数?

15
我有以下Perl脚本,用于统计字符串中F和T的数量:
my $str = "GGGFFEEIIEETTGGG";
my $ft_count = 0;
$ft_count++ while($str =~ m/[FT]/g);
print "$ft_count\n";

有没有更简洁的方法来获取计数(换句话说,将第2行和第3行合并)?

4个回答

28
my $ft_count = $str =~ tr/FT//;

参见 perlop

如果REPLACEMENTLIST为空,则复制SEARCHLIST。后者对于计算类中的字符很有用…

  $cnt = $sky =~ tr/*/*/;     # count the stars in $sky
  $cnt = tr/0-9//;            # count the digits in $_

这是一个基准测试:

use strict; use warnings;

use Benchmark qw( cmpthese );

my ($x, $y) = ("GGGFFEEIIEETTGGG" x 1000) x 2;

cmpthese -5, {
    'tr' => sub {
        my $cnt = $x =~ tr/FT//;
    },
    'm' => sub {
        my $cnt = ()= $y =~ m/[FT]/g;
    },
};
        比率     tr      m
     比率     m    tr
m   108/s    --  -99%
tr 8118/s 7440%    --

这是在32位Windows XP上使用ActiveState Perl 5.10.1.1006时的结果。

与此相比,使用以下命令:

C:\Temp> c:\opt\strawberry-5.12.1\perl\bin\perl.exe t.pl
      比率      m     tr
m   88.8/s     --  -100%
tr 25507/s 28631%     --

它计算括号。必须是 tr/FT// - Toto
3
当然,tr///不是正则表达式,所以“从技术上讲”,它并没有回答具体的问题 :-) 不过,使用tr///比使用正则表达式要好得多。 - ishnid
4
你的基准测试是只找到第一个匹配项的“m”情况,因为正则表达式匹配处于标量上下文中。如果我将该行修复为“my $cnt = () = $y =~ m / [FT] / g;”,“tr”效率会提高约3000%(在我的Linux机器上)。 顺便说一句,原始代码大约比“m”快两倍。 - aschepler
3
@Sinan 提出使用 tr///,我赞同。我认为你的基准测试有一个bug。为了通过正则表达式计数替换次数,需要添加一个中间列表上下文:my $cnt = ()= $y =~ m/[FT]/g;。以这种方式运行时,tr///m//快得多。我也在ActivePerl下使用v5.10版本。 - FMc
2
这就是为什么在基准测试之前先进行测试是个好主意。我通常会将要测试的lambda函数存储到一个哈希表中,遍历哈希表并打印其返回值,然后再进行基准测试。如果任何值不同,那么我就知道基准测试有问题了。@Sinan Ünür - Chas. Owens
显示剩余2条评论

9
当 "m" 操作符带有 /g 标志且在列表上下文中执行时,它会返回匹配子字符串的列表。因此,另一种方法是这样的:
my @ft_matches = $str =~ m/[FT]/g;
my $ft_count = @ft_matches; # count elements of array

但这仍然是两行。另一个更奇怪的技巧是可以使它更短:

my $ft_count = () = $str =~ m/[FT]/g;

"() =" 强制 "m" 处于列表上下文。将具有 N 个元素的列表分配给零变量的列表实际上并不会做任何事情。但是当此分配表达式在标量上下文 ($ft_count = ...) 中使用时,右 "=" 运算符从其右侧返回元素的数量 - 正是您想要的。
这在第一次遇到时非常奇怪,但 "=()=" 是一个有用的 Perl 技巧,用于“在列表上下文中评估,然后获取列表大小”。
注意:我没有关于在处理大字符串时哪个更有效的数据。实际上,我认为您的原始代码在这种情况下可能最好。"

非常有用,可以解释这种奇怪但有效的语法。 - MattTT

8

2
也被称为goatse操作符 ;) =()= - Daenyth
哦,天啊,为什么……非常感谢你,Daenyth...... :O 这一切都是为了告诉大家,信息学家确实很疯狂。 - Sos

0

您可以将第2、3和4行合并成一行,如下所示:

my $str = "GGGFFEEIIEETTGGG";
print $str =~ s/[FT]//g; #Output 4;

2
作为对另一个答案的评论,这更适合作为评论而不是答案 :) - ysth
@ysh,感谢您的评论。我没有意识到我的答案实际上是对另一个答案的评论,是吗?OP问道[有没有更简洁的方法来获取计数(换句话说,结合第2和第3行),这是我对问题的回答。是否已经有人提到了我建议的内容? - Mike
@ysth,原帖可能是这个的重复[https://dev59.com/5XI-5IYBdhLWcg3wcn7m#1850686],我已经在那个问题中发布了类似的解决方案。我认为这篇文章可以与那篇文章合并。 - Mike

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