【问题标题】:Matching optional string in regex匹配正则表达式中的可选字符串
【发布时间】:2014-03-10 14:15:44
【问题描述】:

我在匹配正则表达式中的 optional 模式组时遇到问题。 元字符 * 和 + 是贪婪的,所以我认为元字符?也会很贪心,但它似乎不像我想的那样运作。

理论上我假设如果我们选择使模式组可选,如果模式组在字符串中找到,它将在匹配结果中返回,如果没有找到我们仍将获得整体匹配结果,但结果中缺少此匹配项。

实际发生的情况是,如果我的模式在字符串中匹配,它不包含在匹配结果中,正则表达式似乎注意到模式组是可选的,甚至不费心去尝试匹配它。

如果我们设置一个测试并将这个可选模式组更改为非可选,正则表达式会将其包含在匹配结果中,但这仅适用于测试,因为有时该模式在字符串中不可用。

我之所以需要将匹配结果包含在结果中,是因为我需要匹配结果以供日后分析。

Encase 我没有很好地描述这个场景,我在 PHP 中设置了一个非常简单的示例。

$string = 'This is a test, Stackoverflow. 2014 Cecili0n';

if(preg_match_all("~(This).*?(Stackoverflow)?~i",$string,$match))
    print_r($match);

结果

Array
(
    [0] => Array
        (
            [0] => This
        )

    [1] => Array
        (
            [0] => This
        )

    [2] => Array
        (
            [0] => 
        )
)

(Stackoverflow)? 是可选模式,如果我们运行上面的代码,即使该模式在字符串中可用,它也不会在匹配结果中返回。

如果我们将此模式组设为强制性,它将在结果中返回,如下所示。

if(preg_match_all("~(This).*?(Stackoverflow)~i",$string,$match))
    print_r($match);

结果

Array
(
    [0] => Array
        (
            [0] => This
        )

    [1] => Array
        (
            [0] => This
        )

    [2] => Array
        (
            [0] => Stackoverflow
        )
)

我怎样才能做到这一点?获得有关如何找到匹配项的准确数据对我来说很重要。

感谢您对此事的任何想法。

【问题讨论】:

  • 哇,非常好的问题。我完全感到困惑,也想知道这里实际发生了什么。注意:这不是 PHP 特有的问题,而是一般的 RegEx 问题。
  • @tenub 我过去遇到过这个问题,并花了几个小时尝试许多不同的替代方案,例如更深的嵌套子组、指定最小间隔组等等,最后我相信我只是把它遗漏了,现在问题又回来了,但这次我不能离开它。

标签: php regex


【解决方案1】:

这里发生了什么

这可能令人惊讶,但这实际上是预期的行为。让我们分解正则表达式并将其翻译成人类可读的术语:

(This)               Match "This" literally
.*?                  Match any character **as few times as possible**,
                     while still allowing the rest of the expression to match
(Stackoverflow)?     Match "Stackoverflow" literally **if possible**

那么会发生什么:

  • 正则表达式引擎匹配“This”。
  • 然后它必须考虑*? 量词应该匹配多少个字符。
  • 假设我们匹配零个字符。这是否允许表达式的其余部分匹配?换句话说,(Stackoverflow)? 是否匹配“是一个测试,Stackoverflow.2014 Cecili0n”?
  • 子模式是可选的,所以它是可选的!因此,.*? 匹配零个字符。
  • 最终的子模式(Stackoverflow)? 匹配什么?显然在尝试匹配的位置没有任何东西。

最终结果:两个量化子模式都匹配空字符串。

如何得到预期的结果

如果将所有内容都设为可选不起作用,您如何选择匹配“Stackoverflow”?通过明确说明正则表达式引擎的可接受选项:

~(This)(.*?(Stackoverflow)|.*?)~i

这指示引擎要么尽可能多地匹配文字“Stackoverflow”,要么尽可能多地匹配。通过首先列出“包含 Stackoverflow”选项,您可以确保如果它确实存在于文本中,它将被匹配。

显然.*? 选项在此示例中没有太大意义,但我将保留它,因为我想描述一种无论实际正则表达式如何都可以工作的“机械”转换。

请注意,为了保持与原始正则表达式的完全等价,必须将用于结构目的的额外组设为非捕获:

~(This)(?:.*?(Stackoverflow)|.*)~i

See it in action.

【讨论】:

  • @Jon,谢谢,我仍在研究您的答案,但是在开头段落的第三个要点中,您说我使用“。*?”,因为它是一个懒惰的版本,零匹配也许可以接受,但是即使我用“。*”使这个模式变得贪婪,它与缺失组的结果完全相同。我会认为贪婪的匹配应该偶然发现(Stackoverflow)?,因为它被告知尽可能匹配,但它仍然没有,
  • @cecilli0n:现在更容易解释了。 .* 以相反的方式工作:它首先假设它匹配到字符串末尾的所有内容。那么,既然整个正则表达式都必须匹配,你能在字符串末尾匹配(Stackoverflow)?吗?当然可以,这是可选的。如此贪婪的.* 匹配所有内容,而可选的子模式再次空手而归。
【解决方案2】:

我已经对此进行了实验,但似乎无法破解它。同时,一种可行的选择是进行两次测试,如下例所示

$string = 'This is a test, Stackoverflow. 2014 Cecili0n';
$pattern1 = "~(This).*?(Stackoverflow)~i";
$pattern2 = "~(This).*?~i";

if(preg_match_all($pattern1,$string,$match)) {
    print_r($match);
} elseif(preg_match_all($pattern2,$string,$match)) {
    print_r($match);
}

当我找到更好的东西时,我会更新答案。

【讨论】:

  • 这并不能真正解决问题。
  • @kizer 过去我相信我已经采用了这样的解决方案,它确实有效,但是当你有很多正则表达式时它会变成一个糟糕的解决方案,更新一组意味着必须改变一切。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-05
相关资源
最近更新 更多