如何编写消除数字和冒号之间的 space 的正则表达式?

How do I write a regex that eliminates the space between a number and a colon?

我想替换一个或两个数字之间的 space 和后跟 space、数字或行尾的冒号。如果我有一个字符串,

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)/, ':')
# => "  0: 28 : 37.02"

好像匹配第一个":",但是第二个":"不匹配。我不明白为什么。

问题

我将用注释定义您的正则表达式(在自由间距模式下)以显示它在做什么。

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

请注意 '|' 不是字符 class 中的特殊字符 ("or")。它被视为普通字符。 (即使 '|' 在字符 class 中被视为 "or",那也没有用,因为字符 classes 用于强制匹配其中的任何一个字符.)

假设

line = "   0 : 28 : 37.02"

然后

line.gsub(r, ':')
   #=> "  0: 28 : 37.02"
 #=> " "
 #=> "0"
 #=> " "

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

开头的三个 space 之一

下一个捕获组 2 捕获 "0"。为此,捕获组 1 必须捕获 line 的索引 2 处的 space。然后再匹配一个 space 和一个冒号,最后,捕获组 3 将冒号后面的 space

子串' 0 : '因此被替换为':' #=> '0: ',所以gsubreturns" 0: 28 : 37.02"。请注意 '0' 之前的一个 space 被删除了(但应该保留)。

一个解决方案

以下是如何删除一个或多个 Unicode 白色字符中的最后一个 space 字符,这些字符前面有一个或两个数字(但不能更多),并且在字符串末尾跟一个冒号或冒号后跟白色 space 或数字。 (哇!)

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

正则表达式为 "match one or more digits followed by one or more whitespace characters, followed by a colon (all these characters are matched), not followed (negative lookahead) by a character other than a unicode whitespace or digit"。如果匹配,我们检查开头有多少位数字。如果超过两个则返回匹配项(无变化),否则从匹配项中删除冒号前的白色 space 字符并返回修改后的匹配项。

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"

如果像示例中那样,字符串中的唯一字符是数字、白色space和冒号,则不需要回顾。

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

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

排除第三个数字可以用负回溯来完成,但由于其他一两个数字的长度可变,你不能对该部分使用正回溯。

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