所以你想知道它是如何工作的......
正则表达式对字符串中数字的值没有真正的理解,它只关心它们是如何表示的,这就是为什么在一个范围内查找数字似乎比它应该的更尴尬的原因。您的正则表达式引擎可以理解character class 中的范围(如[0-9])的唯一原因是因为字符在a list 中的位置([&-~] 之类的字符范围同样有效,并且同样可以理解.)
因此,要匹配 99-9999999 这样的范围,您必须拼出它的样子:字面意思“99”,或者没有前导零的三位数字,或者没有前导零的四位数字,等等。
但这就是你的demo 所做的,对吧?它没有用。在您的测试字符串“9293”中,您的正则表达式仅匹配“929”。这里发生的事情是正则表达式引擎渴望返回一个完整的匹配 - 一旦它找到一个它就会返回它,即使稍后可能会发生更好/更长的匹配。
比赛是这样发生的。 (我会跳过一些细节,比如grouping,因为它们在这里不是很相关。)
第 1 步。
引擎将正则表达式中的第一个标记与字符串中的第一个字符进行比较
(<strong><em><kbd>9</kbd></em></strong>9|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])
<kbd><strong><em>9</em></strong></kbd>293✅
成功,他们匹配。
第 2 步。
引擎然后前进到正则表达式中的下一个标记和字符串中的下一个字符并进行比较。
(<strong><em>9</em></strong><kbd>9</kbd>|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])
<strong><em>9</em></strong><kbd>2</kbd>93❌
失败,不匹配。引擎会在此处停止并返回失败,但您使用的是alternation via |,因此它知道可以尝试替代表达式。
第 3 步。
引擎前进到正则表达式中下一个替代表达式的第一个标记,并倒回字符串中的位置。
(99|<strong><em><kbd>[1-9]</kbd></em></strong>[0-9][0-9]|[1-9][0-9][0-9][0-9])
<strong><em><kbd>9</kbd></em></strong>293✅
成功,他们匹配。
第 4 步。
继续。
(99|<strong><em>[1-9]<kbd>[0-9]</kbd></em></strong>[0-9]|[1-9][0-9][0-9][0-9])
<strong><em>9<kbd>2</kbd></em></strong>93✅
匹配。
第 5 步。
又一次。
(99|<strong><em>[1-9][0-9]<kbd>[0-9]</kbd></em></strong>|[1-9][0-9][0-9][0-9])
<strong><em>92<kbd>9</kbd></em></strong>3✅
成功。完整的表达式匹配。没有必要尝试剩下的替代品。这里返回的匹配是:
<strong><em>929</em></strong>
您可能已经知道,如果您的输入字符串改为“9923”,那么第 2 步将匹配并且那里的引擎将有 stopped and returned "99"。
您也可能已经发现,如果您将备用表达式从最长到最短重新排列
([1-9][0-9][0-9][0-9]|[1-9][0-9][0-9]|99)
将首先尝试最长的,即match and return your expected "9293"。
简化
但它仍然很冗长,尤其是当您增加范围内的位数时。您可以做几件事来简化它。
字符类[0-9]可以用shorthand character class\d来表示。
([1-9]\d\d\d|[1-9]\d\d|99)
不要重复它们,而是在大括号中使用quantifier,如下所示:
([1-9]\d{3}|[1-9]\d{2}|99)
碰巧,量词也可以采用{min, max} 的形式,因此您可以将两个相似的替代词结合起来:
([1-9]\d{2,3}|99)
您可能希望这会让您再次返回“929”,引擎非常渴望,但量词默认为greedy,因此他们会尽可能多地获取。这非常适合您更大的期望范围:
([1-9]\d{2,6}|99)
完成
你从这里用它做什么取决于你需要正则表达式做什么。就目前而言,括号是多余的,创建整个正则表达式本身的capturing group 是没有意义的。但是,当您输入如下字符串时,就会做出决定:
你很可能会被 1000 格鲁吃掉。
如果你想找出有多少格鲁将要吃掉你,你可以使用
[1-9]\d{2,6}|99
这将是return 1000。
但是,这种排序又回到了您的演示的原始问题。如果它是“12345678 grue”,超出范围,这将匹配“1234567”,这可能不是你想要的。您可以使用negative lookarounds 确保您匹配的号码后面没有紧跟(或前面)另一个数字。
(?<!\d)([1-9]\d{2,6}|99)(?!\d)
(?<!\d) 表示“从这个位置开始,前一个字符不是数字”,而(?!\d) 表示“从这个位置开始,下一个字符不是数字”。
替代项周围的括号又回来了,因为它们是此处分组所必需的,否则后瞻将仅是第一个替代表达式的一部分并应用于第一个替代表达式,而前瞻将仅是第二个替代表达式的一部分并应用于第二个替代表达式。
另一方面,如果您要确保整个字符串 only 由您范围内的数字组成,您需要改用 anchors ^ 和 @987654363 @(分别是字符串的开头和结尾):
^([1-9]\d{2,6}|99)$
最后你可以将捕获组换成non-capturing group (?:...),所以:
^(?:[1-9]\d{2,6}|99)$
或
(?<!\d)(?:[1-9]\d{2,6}|99)(?!\d)
您仍然会抓取号码作为匹配项,只是不会在组抓取中重复。 (环视已经是非捕获的,无需担心这些。)