在找到特定单词之后,统计特定单词出现的次数。

3

我对正则表达式比较陌生,现在遇到了问题,我想使用preg_match_all计算在world之后出现的hello的数量

如果我使用"world".+(hello),它会计算最后一个hello中的内容;"world".*?(hello)会停止在第一个hello处,两者都只会计算一次。

blah blah blah
hello
blah blah blah
class="world" 
blah blah blah
hello 
blah blah
hello
blah blah blah
hello
blah blah blah

我期望您的计数结果为3,因为在world之前的hello不应该被计算。

Wiktor在5年前展示了所需的模式 @ Regex to match specific words after one word - mickmackusa
4个回答

2

使用简单的正则表达式的另一个选项:

if(preg_match('/"world".*/s', $str, $out)) {
  echo preg_match_all('/\bhello\b/', $out[0]);
}

See demo at tio.run

在tio.run上查看演示


1
这不是和 @Tim Biegeleisen 建议的做法一样吗?先抓取 world 后面的所有内容,然后计算 hello 整个单词的数量吗? - limestreetlab
1
@deanstreet 这个程序会 1.) 匹配 "world"... 和之后的任何内容 2.) 如果匹配成功,它会在输出中计算所有的 hello。必须确保字符串中存在 "world",并且 hello 出现在 "world" 之后才能被计算。 - bobble bubble
作为新手,这种分而治之的方法肯定比上面建议的一行正则表达式更容易理解,但那一行也非常好用。 - limestreetlab

1

另一种方法:强制模式失败,并且如果字符串中不存在world,则不进行重试:

~(?:\A(*COMMIT).*?world)?.*?hello~s

演示

非捕获组是可选但贪婪的。因此,每次尝试模式时都会测试它。
它以匹配字符串开头的锚点\A开始,因此这是该组能够成功的唯一位置。在字符串的其他位置,\A失败,并且由于该组是可选的,其中剩余的子模式被忽略,继续使用.*?hello进行搜索。
紧接着,有一个回溯控制动词(*COMMIT),如果在它之后失败,则强制不再重试模式。(故事结束)。

换句话说,如果该组在字符串开头失败,则研究将立即终止。

优点:它需要比基于\G的模式更少的步骤。


为了更有效率,可以使用可选组而不是交替来编写基于\G的模式:

~(?:\A.*?world)?(?!\A).*?hello~sA

这里的A修饰符扮演了\G锚点的角色,但它与在模式的每个分支(这里只有一个)以\G锚点开头完全相同。


1
您可以在此处使用单个preg_match_all调用:
$text = "blah blah blah\nhello\nblah blah blah\nclass=\"world\" \nblah blah blah\nhello \nblah blah\nhello\nblah blah blah\nhello\nblah blah blah";
echo preg_match_all('~(?:\G(?!^)|\bworld\b).*?\K\bhello\b~s', $text);

请查看正则表达式演示PHP演示。细节如下:
  • (?:\G(?!^)|\bworld\b) - 上一次匹配的结尾(\G(?!^)进行检查:\G匹配字符串的开始或者上一次匹配的位置,因此我们需要排除字符串的开始位置,使用(?!^)负向前瞻实现)或整个单词world
  • .*? - 任意零个或多个字符,尽可能少地匹配
  • \K - 抛弃到目前为止匹配的所有文本
  • \bhello\b - 整个单词hello

注意:如果您不需要单词边界检查,则可以从模式中删除\b

如果helloworld是用户定义的模式,则必须在模式中使用preg_quote函数对它们进行转义:

$start = "world";
$find = "hello";
$text = "blah blah blah\nhello\nblah blah blah\nclass=\"world\" \nblah blah blah\nhello \nblah blah\nhello\nblah blah blah\nhello\nblah blah blah";
echo preg_match_all('~(?:\G(?!^)|' . preg_quote($start, '~') . '\b).*?\K' . preg_quote($find, '~') . '~s', $text);

它按预期工作,但就我个人而言,我很难理解它。(?!^)是什么意思?我发现排除它将包括world之前的匹配项。 - limestreetlab
1
@deanstreet 我在回答中加入了这个解释。此外,查看在上一次匹配结束后继续。如果您想了解更多关于\G的内容,请查看我的有关\G用例的YouTube视频 - Wiktor Stribiżew
1
我认为你不需要在这里使用\K ;) - bobble bubble
@bobblebubble 这并不是最重要的,不是必须的,但以后可能会成为救命稻草 :) - Wiktor Stribiżew
1
有些东西看起来很熟悉:https://dev59.com/mZTfa4cB1Zd3GeqPUrrf#35792544 - mickmackusa
@mickmackusa,没错,这是关于在另一个单词后替换单词的问题。还有一个与使用Unicode字符串和preg*函数相关的问题。 - Wiktor Stribiżew

0

一种方法可能是首先剥离字符串的前导部分,直到包括第一个出现的world为止。然后像你已经在做的那样调用preg_match_all并获取hello出现次数的计数。

$input = "blah blah blah
hello
blah blah blah
class=\"world\" 
blah blah blah
hello 
blah blah
hello
blah blah blah
hello
blah blah blah";

$input = preg_replace("/^.*?\bworld/", "", $input);
preg_match_all("/\bhello\b/", $input, $matches);
echo sizeof($matches[0]);  // 4

2
这个答案有缺陷,因为如果字符串中不存在触发词(world),它将失败。即使缺少world,输出仍然是4。证明:https://3v4l.org/58vXC - mickmackusa

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