为什么我对正则表达式的扫描没有发现超过 1 个匹配项?[复制]

How come my scan of a regex is not picking up more than 1 match? [duplicate]

提问人:Henley Wing Chiu 提问时间:11/9/2023 更新时间:11/10/2023 访问量:95

问:

我有一个正则表达式,它应该提取单词“year”之前 7 个“单词”或更少的任何数字。当我在字符串上运行它时,它不知何故只返回 1 匹配项,而不是 2。为什么它没有返回第 2 场比赛?正则表达式本身确实与句子的第二部分匹配,但是使用扫描时,它似乎没有返回它

str = "Just 5 years of experience and 12 years of experience ok" 
regex = /(\d+)(?:\s+\S+){0,7}\s*year/i
str.scan(regex) # returns just [["5"]] 


regex.match("5 years of experience")
=> #<MatchData "5 years" 1:"5"> 
regex.match("12 years of experience") #regex does match the 2nd part but scan misses it
=> #<MatchData "12 years" 1:"12"> 
正则表达式 Ruby

评论

0赞 anubhava 11/9/2023
尝试:/(\d+)(?:\s+\S+){0,7}?\s*year/i
0赞 engineersmnky 11/9/2023
您可以使用 regex101 来了解正则表达式的实际含义(右侧)。此外,根据您的输入,您可能已经大大复杂化了/(\d+)\s+years?/i
1赞 Henley Wing Chiu 11/9/2023
该输入只是 1 个示例。其他例子可能是“5 年及以上的经验”
2赞 Stefan 11/9/2023
@sln完全没有关系?链接问题的公认答案表明“你需要使你的正则表达式变得懒惰/非贪婪”,这也是你在下面的答案中提出的:“只需使用非贪婪运算符”。
1赞 Todd A. Jacobs 11/10/2023
@sin 评论不用于扩展讨论。Stefan 的观点是,一个可能的答案在一个相关的问题中得到了体现,虽然我也不同意这种观点,但解决这个问题的正确方法是投票支持标记关闭/重新开放,而不是让它变得个人化。你的评论语气有点过分,有可能把意见分歧变成人身攻击。我们都在努力以自己的方式提供帮助;请记住这一点。—请考虑删除您的评论,我会很高兴地删除这个评论,或者将其标记为“不再需要”。

答:

0赞 Todd A. Jacobs 11/9/2023 #1

扫描提供不同的捕获和非捕获行为

使用捕获组时,String#scan 仅返回与捕获组匹配的匹配项。目前,不在捕获组中,并且显式未捕获,因此不会返回。文档明确指出:\s*year(?:\s+\S+){0,7}

如果模式包含组,则每个结果都是一个数组,每个组包含一个条目。

您还通过限制数字后面的内容和使用字符来过度限制捕获组的上下文,因此第二组数字永远不会匹配。请参阅积极的展望,了解可以在不使用字符的情况下指定上下文的替代方案。

假设您不想在捕获中包含该单词,这将起作用:years

string = "Just 5 years of experience and 12 years of experience ok"
regexp = /
  (?ix)  # set case-insensitive and extended modes
  (\d+)  # capture one or more digits when
  \s+    # ...followed by at least one whitespace character
  years? # ...followed by "year" or "years"
  \b     # ...followed by a word boundary
/

string.scan regexp
# => [["5"], ["12"]]

对于更紧凑的表达式,请将内联模式移到主正则表达式之外,并将它们追加到分隔正则表达式文本的字符中。具体而言,您通常不需要将扩展模式用于生产用途(它主要用于记录表达式),因此您可以像这样收紧正则表达式,同时返回与上面所示的扩展模式相同的结果:(?ix)

/(\d+)\s+years?\b/i

将扫描结果转换为整数

虽然不是原始问题的一部分,但您很可能希望扫描的最终结果为数字而不是字符串。如果是这种情况,只需修改扫描以 #flatten 结果,然后将 Array 成员强制为 Integers。例如:

string = "Just 5 years of experience and 12 years of experience ok"
regexp = /(\d+)\s+years?\b/i

string.scan(regexp).flatten.map &:to_i
# => [5, 12]

虽然这超出了您最初问题的范围,但这是合乎逻辑的下一步。我在这里介绍它,希望它能为您省去处理嵌套的 String 数组对象的麻烦,您将从使用 Sring#scan 和捕获中获得。

-1赞 sln 11/9/2023 #2

enter image description here

只需在范围内使用非贪婪运算符{0,7}?

(\d+)(?:\s+\S+){0,7}?\s*years?

https://regex101.com/r/gHX25u/1

概述

( \d+ )                       # (1)
(?: \s+ \S+ ){0,7}?
\s* years?

评论

0赞 Todd A. Jacobs 11/10/2023
我不会对你的答案进行投票,但我想指出的是,仅仅展示一个非贪婪的原子如何提供正确的匹配并不能真正解释OP的潜在问题或解决方案。它还错过了简化表达式的机会,并在 String#scan 的特定上下文中提供解决方案。也就是说,我不会对有效的答案投反对票;我只是觉得教别人如何钓鱼比教他们钓鱼要好。YMMV。
0赞 sln 11/10/2023
@ToddA.Jacobs:我想给出某人正则表达式的所有后果。我可以,但手头的问题是.这就是我从中得到的意图。我不是来提供正则表达式课程的。我不写正则表达式宣言。{0,7}?
0赞 Todd A. Jacobs 11/10/2023
{0,7}?不是不贪婪。 是一个消耗原子,意思是“零或一”。它遵循一个范围运算符,表示“前一组的0到7”,这一事实只会使原子更加混乱,但这并不能改变两者本质上都不是贪婪的事实,尽管覆盖意外非空白的范围可能会过度消耗。?
0赞 sln 11/10/2023
@ToddA.Jacobs:一直都是这样。 本身意味着 0 或 1。它量化了前面的值。它是一个量词。 量词后面是修饰语,它的意思是尽可能少的次数。 是一个量词。在它后面添加一个意味着它修改了量词的行为。这也是一个很好的例子:所有的量词本质上都是贪婪的。??{0,7}???
0赞 Todd A. Jacobs 11/10/2023
{0,7}是一个范围运算符,它是一种量词。 是零或一范围,但两者都引用它们正在量化的原子。与其争论贪婪的语义,不如说三重网络是贪婪最好在捕获组或非捕获组中固定,或者重构匹配,而不是限制匹配范围的数量。如果人们想要的只是“修复我的代码”,那么 CoPilot 或 ChatGPT 同样能够提供答案。我将把它留在那里,以避免对定义或语义的进一步争论。{0,7}?