如何计算Perl中重叠的子串?

sfa*_*tor 7 string perl count

我需要实现一个程序来计算perl中字符串中子字符串的出现次数.我已经实现如下

sub countnmstr
{
  $count =0;
  $count++ while $_[0] =~ /$_[1]/g;
  return $count;
}

$count = countnmstr("aaa","aa");

print "$count\n";
Run Code Online (Sandbox Code Playgroud)

现在这就是我通常会做的事情.但是,在上面的实现中,我想计算'aaa'中'aa'的出现次数.在这里,我得到的回答为1似乎是合理的,但我也需要考虑重叠的情况.因此,上述情况应该给出答案为2,因为如果我们考虑重叠,则有两个'aa'.

任何人都可以建议如何实现这样的功能?

bri*_*foy 12

每个人的答案都变得非常复杂了(噢!daotoad应该把他的评论作为答案!),也许是因为他们害怕山羊运营商.我没有说出来,这正是人们所说的.它使用了一种技巧,即列表赋值的结果是右侧列表中的元素数量.

用于计算匹配的Perl习语是:

 my $count = () = $_[0] =~ /($pattern)/g;
Run Code Online (Sandbox Code Playgroud)

山羊部分是= () =,这是两个任务中间的空列表.山羊的左手部分从山羊的右侧获得计数.请注意,您需要在模式中捕获,因为这是匹配运算符将在列表上下文中返回的列表.

现在,你的下一个技巧是你真的想要一个积极的外观(或者可能是前瞻).外观不消耗字符,因此您无需跟踪位置:

 my $count = () = 'aaa' =~ /((?<=a)a)/g;
Run Code Online (Sandbox Code Playgroud)

aaa的只是一个例子.如果您有可变宽度图案,则必须使用前瞻.Perl中的Lookbehinds必须是固定宽度.

  • 如果您不知道为什么它被称为"山羊运营商",请不要*试图找出它意味着什么.有些事情你最好不知道. (7认同)
  • "有些东西不可见" (2认同)

Sin*_*nür 8

请参阅ysth的回答 ......我没有意识到该模式可能只包含零宽度断言,并且仍然可以用于此目的.

您可以按照其他人的建议使用正向前瞻,并将函数编写为:

sub countnmstr {
    my ($haystack, $needle) = @_;
    my ($first, $rest) = $needle =~ /^(.)(.*)$/;
    return scalar (() = $haystack =~ /(\Q$first\E(?=\Q$rest\E))/g);
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用它pos来调整下一个搜索的位置:

#!/usr/bin/perl

use strict; use warnings;

sub countnmstr {
    my ($haystack, $needle) = @_;
    my $adj = length($needle) - 1;
    die "Search string cannot be empty!" if $adj < 0;

    my $count = 0;
    while ( $haystack =~ /\Q$needle/g ) {
        pos $haystack -= $adj;
        $count += 1;
    }
    return $count;
}

print countnmstr("aaa","aa"), "\n";
Run Code Online (Sandbox Code Playgroud)

输出:

C:\Temp> t
2


yst*_*sth 8

sub countnmstr
{
    my ($string, $substr) = @_;
    return scalar( () = $string =~ /(?=\Q$substr\E)/g );
}

$count = countnmstr("aaa","aa");

print "$count\n";
Run Code Online (Sandbox Code Playgroud)

几点:

//g 在列表上下文中匹配尽可能多的次数.

\Q...\E 用于自动转义任何元字符,以便您进行子字符串计数,而不是子模式计数.

使用前瞻(?= ... )导致每个匹配不"消耗"任何字符串,允许在下一个字符处尝试以下匹配.

这使用相同的功能,其中标量上下文中的列表赋值(在本例中为空列表)返回列表赋值右侧的元素计数作为goatse/flying-lentil/spread-eagle /无论运算符,但是使用标量()而不是标量赋值来提供标量上下文.

$_[0]不是直接使用,而是复制到词汇; 如果传递的字符串已经存储$_[0],$string那么天真地使用代替将导致在//g字符串中途而不是在开头处开始pos().

更新:s /// g更快,但不如使用索引快:

sub countnmstr
{
    my ($string, $substr) = @_;
    return scalar( $string =~ s/(?=\Q$substr\E)//g );
}
Run Code Online (Sandbox Code Playgroud)