首页 > 解决方案 > 99 到 9999999 之间的数字正则表达式

问题描述

我正在尝试生成一个正则表达式,它将匹配 99 和 9999999 范围内的任何数字。我无法理解生成数字范围的一般工作原理。我设法在网上找到了一个可以为我完成这项工作的范围生成器,但我想了解它的实际工作原理。

我尝试做这个范围如下:

(99|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])

这应该匹配 99、任何 3 位数字或任何 4 位数字,但它不能按预期工作。经测试,它仅匹配数字 99 和 3 位数字。四位数字根本不匹配。如果我只将 4 位数字的部分单独写为

[1-9][0-9][0-9][0-9]

它匹配 4 位数字,但是当我按照第一个示例构建它时,它不起作用。有人可以给我一些澄清,这实际上是如何工作的,以及如何成功地为 99 到 9999999 的范围生成正则表达式。

演示链接 -这里

标签: regexpcreregular-language

解决方案


所以你想知道这是如何工作的......

正则表达式对字符串中数字的值没有真正的理解,它只关心它们是如何表示的,这就是为什么在一个范围内寻找数字似乎比它应该的更尴尬的原因。您的正则表达式引擎完全可以理解字符类中的范围的唯一原因[0-9]是因为字符在列表中的位置(类似的字符范围[&-~]同样有效,并且同样可以理解。)

因此,要匹配 99-9999999 之类的范围,您必须拼出它的样子:文字“99”,或者没有前导零的三位数字,或者没有前导零的四位数字,等等。

但这就是你的演示所做的,对吧?它没有用。在您的测试字符串“9293”中,您的正则表达式仅匹配“929”。这里发生的事情是正则表达式引擎渴望返回一个完整的匹配 - 一旦它找到一个它就会返回它,即使稍后可能会发生更好/更长的匹配。


这场比赛是这样发生的。(我会跳过一些细节,比如grouping,因为它们在这里不是很相关。)

步骤1。

引擎将正则表达式中的第一个标记与字符串中的第一个字符进行比较

(99|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])

9293✅</p>

成功,他们匹配。

第2步。

然后引擎前进到正则表达式中的下一个标记和字符串中的下一个字符,并比较它们。

(99|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])

9293❌</p>

失败,没有对手。引擎将在此处停止并返回失败,但您正在使用替代 via|,因此它知道有一个替代表达式可以尝试。

步骤 3。

引擎前进到正则表达式中下一个备用表达式的第一个标记,并倒回字符串中的位置。

(99|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])

9293✅</p>

成功,他们匹配。

第4步。

继续。

(99|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])

9293✅</p>

匹配。

步骤 5。

然后再次。

(99|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])

9293✅</p>

成功。完整的表达式匹配。没有必要尝试剩下的替代品。这里返回的匹配是:

929

正如您可能已经发现的那样,如果您的输入字符串改为 "9923" ,那么第 2 步将匹配并且那里的引擎将停止并返回 "99"

正如您可能已经发现的那样,如果您将备用表达式从最长到最短重新排列

([1-9][0-9][0-9][0-9]|[1-9][0-9][0-9]|99)

最长的将首先尝试,这将匹配并返回您预期的 "9293"


简化

不过,它仍然很冗长,尤其是当您增加范围内的位数时。您可以做几件事来简化它。

字符类[0-9]可以用简写字符类 \d来表示。

([1-9]\d\d\d|[1-9]\d\d|99)

而不是重复它们,而是使用大括号中的量词,如下所示:

([1-9]\d{3}|[1-9]\d{2}|99)

碰巧,量词也可以采用 的形式{min, max},因此您可以组合两个相似的替代词:

([1-9]\d{2,3}|99)

您可能期望这会让您再次返回“929”,引擎非常渴望,但量词默认情况下是贪婪的,因此它们会尝试尽可能多地获取。这很适合您更大的所需范围:

([1-9]\d{2,6}|99)

整理起来

你从这里用它做什么取决于你需要正则表达式做什么。就目前而言,括号是多余的,创建整个正则表达式本身的捕获组是没有意义的。但是,当您有如下输入字符串时,就会做出决定:

你可能会被 1000 格鲁吃掉。

如果你想找出有多少格鲁将要吃掉你,你可以使用

[1-9]\d{2,6}|99

这将返回 1000

但是,这种排序会回到您演示的原始问题。如果它是“12345678 grue”,超出范围,这将匹配“1234567”,这可能不是你想要的。您可以使用否定的lookarounds确保您匹配的数字没有紧跟(或前面)另一个数字。

(?<!\d)([1-9]\d{2,6}|99)(?!\d)

(?<!\d)表示“从这个位置开始,前一个字符不是数字”,而(?!\d)表示“从这个位置开始,下一个字符不是数字”。

替代项周围的括号又回来了,因为它们是此处分组所必需的,否则后视将仅是第一个替代表达式的一部分并应用于第一个替代表达式,而前瞻将仅是第二个替代表达式的一部分并应用于第二个替代表达式。

另一方面,如果您试图确保整个字符串包含您范围内的数字,您将希望使用锚点 ^$(分别为字符串的开头和字符串的结尾):

^([1-9]\d{2,6}|99)$

最后,您可以将捕获组换成非捕获组(?:...),因此:

^(?:[1-9]\d{2,6}|99)$

或者

(?<!\d)(?:[1-9]\d{2,6}|99)(?!\d)

您仍然会抓住号码作为匹配项,只是不会在组捕获中重复。(环视已经是非捕获的,无需担心这些。)


推荐阅读