2017-09-14 144 views
0

我想要替换一个或两个数字和一个冒号后面跟一个空格,一个数字或行尾的空格。如果我有这样一个字符串,如何编写一个消除数字和冒号之间空格的正则表达式?

line = " 0 : 28 : 37.02" 

的结果应该是:

" 0: 28: 37.02" 

我试过如下:

line.gsub!(/(\A|[ \u00A0|\r|\n|\v|\f])(\d?\d)[ \u00A0|\r|\n|\v|\f]:(\d|[ \u00A0|\r|\n|\v|\f]|\z)/, '\2:\3') 
# => " 0: 28 : 37.02" 

这似乎符合第一":",但第二":"不匹配。我无法弄清楚为什么。

+2

至少对于测试,使用'gsub'而不是'gsub!'。后者修改'line',所以如果你执行它,然后改变你的代码并重新运行它,而不记得重新初始化'line',你会得到一条狗的早餐,这会让你彻底迷惑发生了什么。无论如何,我看不出为什么你要使用爆炸版本。 –

回答

1

排除第三位可能会以负回望来完成,但由于其他的一个或两个数字的长度是可变的,你不能用积极的回溯的一部分。

line.gsub(/(?<!\d)(\d{1,2}) (?=:[ \d\$])/, '\1') 
# => " 0: 28: 37.02" 
+1

非常好,但它需要一个调整。如果'line =“0:”',则返回“line”(而不是'“0:”')。我认为你需要将lookahead写为'(?=:(?:[\ d] | $))''。角色类别中的锚点似乎被忽略。我认为这是因为他们不是角色。当你在它的时候,你可能会改变前面的空间到'[[:space:]]或'\ p {Space}',这样''0 \':'''变成''0:'''。 (请参阅OP的代码空格。) –

+0

您也可以使用'\ K'(忽略所有匹配到目前为止)而不是捕获组和反向引用:'“0:28 \ u00A0:37.02”.gsub(/(?<! \ d {1,2} \ K [[:space: ]](?=:(?:[\ d] | \ z))/,'')#=>“0:28:37.02 “'。 –

+0

我发现我可以使用负面而不是正面的超前视角,就像我在更新的答案中所做的那样。 –

2

问题

我会定义与评论中的正则表达式(在自由空间模式),以显示它在做什么。

r = 
/
(      # begin capture group 1 
    \A      # match beginning of string (or does it?) 
    |      # or 
    [ \u00A0|\r|\n|\v|\f] # match one of the characters in the string " \u00A0|\r\n\v\f" 
)      # end capture group 1 
(\d?\d)     # match one or two digits in capture group 2 
[ \u00A0|\r|\n|\v|\f] # match one of the characters in the string " \u00A0|\r\n\v\f" 
:      # match ":" 
(      # begin capture group 3 
    \d      # match a digit 
    |      # or 
    [ \u00A0|\r|\n|\v|\f] # match one of the characters in the string " \u00A0|\r\n\v\f" 
    |      # or        
    \z      # match the end of the string 
)      # end capture group 3 
/x      # free-spacing regex definition mode 

请注意,'|'不是字符类中的特殊字符(“或”)。它被视为一个普通的角色。 (即使'|'被视为“或”字符类中,会因为字符类用于强迫任何一个字符之内就被匹配起不到任何作用。)

假设

line = " 0 : 28 : 37.02" 

然后

line.gsub(r, '\2:\3') 
    #=> " 0: 28 : 37.02" 
$1 #=> " " 
$2 #=> "0" 
$3 #=> " " 

在捕获组1线(\A)的开头不匹配,因为它不是一个字符,字符只有不匹配(虽然我不知道为什么,不引发异常)。 “或”的特殊字符('|')会导致正则表达式引擎尝试匹配字符串" \u00A0|\r\n\v\f"的一个字符。因此它会匹配字符串line开头的三个空格之一。

下一个捕获组2捕获"0"。为此,捕获组1必须捕获位于索引2 line处的空间。然后再匹配一个空格和一个冒号,最后,捕获组3获取冒号后的空格。

因此将子串' 0 : '替换为'\2:\3' #=> '0: ',所以gsub返回" 0: 28 : 37.02"。请注意,删除'0'之前的一个空格(但应保留)。

溶液

这里是你如何删除的由一个或两个数字(而不是更多)之前和之后是一个冒号在的结束一个或多个Unicode空白字符最后字符串或冒号,后跟空格或数字。 (呼!)

def trim(str) 
    str.gsub(/\d+[[:space:]]+:(?![^[:space:]\d])/) do |s| 
    s[/\d+/].size > 2 ? s : s[0,s.size-2] << ':' 
    end 
end 

正则表达式读,“匹配一个或多个数字,随后通过一个或多个空白字符,后面跟着冒号(所有这些字符匹配的),而不是由一个后跟(负先行)除unicode空格或数字以外的其他字符“。如果有匹配,我们检查一下开头有多少位数。如果有两个以上的匹配被返回(不变),否则冒号前面的空格字符被从匹配中移除并且修改后的匹配被返回。

trim " 0 : 28 : 37.02" 
    #=> " 0: 28: 37.02" xxx 
trim " 0\v: 28 :37.02" 
    #=> " 0: 28:37.02" 
trim " 0\u00A0: 28\n:37.02" 
    #=> " 0: 28:37.02" 
trim " 123 : 28 : 37.02" 
    #=> " 123 : 28: 37.02" 
trim " A12 : 28 :37.02" 
    #=> " A12: 28:37.02" 
trim " 0 : 28 :" 
    #=> " 0: 28:" 
trim " 0 : 28 :A" 
    #=> " 0: 28 :A" 

如果,如在该示例中,字符串中的字符只有数字,空格和冒号,不需要回顾后。

您可以使用Ruby的\p{}构造,\p{Space}来代替POSIX表达式[[:space:]]。两者都匹配一类Unicode空白字符,包括示例中显示的那些字符。

+0

谢谢,但对我的问题的最后一部分 - 我如何考虑因素,如果“:”后面跟着空格,数字或行尾,我只想做匹配? – Dave

+0

谢谢,戴夫。我真的需要更仔细地阅读问题。我也错过了包含两位数以上的数字不匹配的要求。我做了一个编辑。 –

1
" 0 : 28 : 37.02".gsub!(/(\d)(\s)(:)/,'\1\3') 
=> " 0: 28: 37.02" 
+0

在提出的解决方案中,这是我能够立即阅读并了解其功能的唯一解决方案。 – moveson

+0

只有当“:”后面跟着空格,数字或行尾时,你如何应用这个答案? – Dave

相关问题