在提取匹配项时如何在Ruby中验证字符串的格式?

时间:2019-02-15 20:03:42

标签: regex ruby

我想要的

  • 验证字符串是否符合以下格式:/^(#\d\s*)+$/(例如#1 #2)。

  • 使用散列来获取所有数字,例如#<MatchData "1234" 1:"#1" 2:"#2">。它不必是MatchData对象,任何可枚举的数组类型都可以使用。

我的问题

使用match时,它刚好匹配上次出现的情况:

/^(#\d\s*)+$/.match "#1 #2"
# => #<MatchData "#1 #2" 1:"#2">

当我使用扫描时,它可以正常工作:

"#1 #2".scan /#\d/
# => ["#1", "#2"]

但是我不相信我可以验证字符串的格式,因为它会为"aaa #1 #2"返回相同的字符串。

问题

我可以仅用1个方法调用来验证我的字符串与/^(#\d\s*)+$/匹配并捕获#number的所有实例吗?

因为我已经使用宝石红了一段时间,所以我对此问题感到很难过。看起来很简单,但我无法正常工作。

2 个答案:

答案 0 :(得分:3)

是的,您可以使用

s.scan(/(?:\G(?!\A)|\A(?=(?:#\d\s*)*\z))\s*\K#\d/)

请参见regex demo

详细信息

  • (?:\G(?!\A)|\A(?=(?:#\d\s*)*\z))-两种选择:
    • \G(?!\A)-上一次成功比赛的结束
    • |-或
    • \A(?=(?:#\d\s*)*\z)-字符串(\A的开头,其后是0个或多个# +数字+ 0+空格的重复,然后是字符串的结尾
  • \s*-0 +空格字符
  • \K-匹配重置运算符丢弃到目前为止已匹配的文本
  • #\d-一个#字符,然后是一个数字

简而言之:字符串位置的开头首先匹配,但前提是右边的字符串(即整个字符串)与所需的模式匹配。由于该检查是先行执行的,因此正则表达式索引会停留在原先的位置,然后仅在有效匹配之后一直进行匹配,这要归功于\G运算符(它匹配字符串的开头或上一个匹配的结尾,因此(?!\A)用于减去起始字符串的位置。

Ruby demo

rx = /(?:\G(?!\A)|\A(?=(?:#\d\s*)*\z))\s*\K#\d/
p "#1 #2".scan(rx)
# => ["#1", "#2"]
p "#1 NO #2".scan(rx)
# => []

答案 1 :(得分:1)

def doit(str)
  r = /\A#{"(#\\d)\\s*"*str.count('#')}\z/      
  str.match(r)&.captures
end

doit "#1#2 #3 "    #=> ["#1", "#2", "#3"]
doit " #1#2 #3 "   #=> nil

请注意,正则表达式仅取决于字符串中字符'#'的实例数。由于在两个示例中该数字均为3,因此各自的正则表达式相等,即:

/\A(#\d)\s*(#\d)\s*(#\d)\s*\z/

此正则表达式的构造如下。

str = "#1#2 #3 "
n = str.count('#')
  #=> 3
s = "(#\\d)\\s*"*n
  #=> "(#\\d)\\s*(#\\d)\\s*(#\\d)\\s*" 
/\A#{s}\z/ 
  #=> /\A(#\d)\s*(#\d)\s*(#\d)\s*\z/ 

正则表达式为:“匹配字符串的开头,然后是三个相同的捕获组,每个捕获组可选地后面跟空格,然后是字符串的末尾。因此,正则表达式既测试字符串的有效性,又提取字符串。捕获组中所需的匹配项。

如果没有匹配项(&返回match),则需要safe navigation operatornil

OP的注释是对问题的概括,其中井号('#')是可选的。这可以通过如下修改正则表达式来解决。

def doit(str)
  r = /\A#{"(?:#?(\\d)(?=#|\\s+|\\z)\\s*)"*str.count('0123456789')}\z/
  str.match(r)&.captures
end

doit "1 2 #3 "     #=> ["1", "2", "3"] 
doit "1 2 #3 "     #=> ["1", "2", "3"] 
doit "1#2"         #=> ["1", "2"] 
doit " #1 2 #3 "   #=> nil   
doit "#1 2# 3 "    #=> nil 
doit " #1 23 #3 "  #=> nil 

对于包含三位数字的字符串,正则表达式为:

/\A(?:#?(\d)(?=#|\s+|\z)\s*)(?:#?(\d)(?=#|\s+|\z)\s*)(?:#?(\d)(?=#|\s+|\z)\s*)\z/ 

虽然此正则表达式可能会很长,但这并不一定意味着它的效率相对较低,因为前瞻非常局限。