【问题标题】:How to build a case-insensitive regular expression with Regexp.union如何使用 Regexp.union 构建不区分大小写的正则表达式
【发布时间】:2016-07-01 16:56:43
【问题描述】:

我有一个字符串列表,需要使用Regexp#union 从它们构建正则表达式。我需要生成的模式不区分大小写

#union 方法本身不接受选项/修饰符,因此我目前看到两个选项:

strings = %w|one two three|

Regexp.new(Regexp.union(strings).to_s, true)

和/或:

Regexp.union(*strings.map { |s| /#{s}/i })

这两种变体看起来都有些奇怪。

是否可以使用Regexp.union 构造不区分大小写的正则表达式?

【问题讨论】:

  • 请注意,您的第一个选项 Regexp.new(Regexp.union(strings).to_s, true) 返回 /(?-mix:one|two|three)/i,这可能不是您想要的,因为匹配的单词仍然区分大小写 (-i)。

标签: regex ruby


【解决方案1】:

简单的起点是:

words = %w[one two three]
/#{ Regexp.union(words).source }/i # => /one|two|three/i

可能想确保您只匹配单词,因此将其调整为:

/\b#{ Regexp.union(words).source }\b/i # => /\bone|two|three\b/i

为了整洁和清晰,我更喜欢使用非捕获组:

/\b(?:#{ Regexp.union(words).source })\b/i # => /\b(?:one|two|three)\b/i

使用source 很重要。当您创建 Regexp 对象时,它会知道适用于该对象的标志(imx)并将这些标志插入到字符串中:

"#{ /foo/i }" # => "(?i-mx:foo)"
"#{ /foo/ix }" # => "(?ix-m:foo)"
"#{ /foo/ixm }" # => "(?mix:foo)"

(/foo/i).to_s  # => "(?i-mx:foo)"
(/foo/ix).to_s  # => "(?ix-m:foo)"
(/foo/ixm).to_s  # => "(?mix:foo)"

当生成的模式独立时很好,但是当它被插入到字符串中以定义模式的其他部分时,标志会影响每个子表达式:

/\b(?:#{ Regexp.union(words) })\b/i # => /\b(?:(?-mix:one|two|three))\b/i

深入研究 Regexp 文档,您会看到 ?-mix 关闭了 (?-mix:one|two|three) 内部的“忽略大小写”,即使整体模式被标记为 i,导致模式不起作用你想要什么,而且真的很难调试:

'foo ONE bar'[/\b(?:#{ Regexp.union(words) })\b/i] # => nil

相反,source 删除了内部表达式的标志,使模式符合您的预期:

/\b(?:#{ Regexp.union(words).source })\b/i # => /\b(?:one|two|three)\b/i

'foo ONE bar'[/\b(?:#{ Regexp.union(words).source })\b/i] # => "ONE"

可以使用Regexp.new 并传入标志来构建您的模式:

regexp = Regexp.new('(?:one|two|three)', Regexp::EXTENDED | Regexp::IGNORECASE) # => /(?:one|two|three)/ix

但随着表达式变得越来越复杂,它变得笨拙。使用字符串插值构建模式仍然更容易理解。

【讨论】:

  • 这很有趣。我没想到"Twosome" =~ /\b#{ Regexp.union(words).source }\b/i #=> 0。因此需要将#{ Regexp.union(words).source } 放在一个组中。我也不知道source
  • source,当然,谢谢!很好的解释,顺便说一句。
  • 我使用很多模式来解析文本,source 是能够将简单模式组合成更复杂模式的核心。
【解决方案2】:

你忽略了显而易见的事情。

strings = %w|one two three|

r = Regexp.union(strings.flat_map do |word| 
  len = word.size
  (2**len).times.map { |n|
    len.times.map { |i| n[i]==1 ? word[i].upcase : word[i] } }
end.map(&:join))

 "'The Three Little Pigs' should be read by every building contractor" =~ r
   #=> 5      

【讨论】:

  • 那行得通,是的;更重要的是:经过适当修补的 String#upcase 甚至可以与 utf8 字符串一起使用,就像西里尔字母一样(而标准的 Ruby 正则表达式仍然缺少此功能。)
  • 这是一个荒谬的答案。如果可以的话,我会否决它。假设r 是为strings = ["one", "two", "three hundred seventy"] 计算的正则表达式。当然,它有效 ("There were tHRee HuNDred sevENTy cats" =~ r #=> 11),但 r.to_s.count('|') #=> 2097167!
  • 我知道,我知道 ;) 我不会在产品中使用它,只在家里使用。
  • TIL 可以使用索引访问从整数中获取位:tada:
猜你喜欢
  • 1970-01-01
  • 2022-01-09
  • 2011-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多