【问题标题】:Ruby super-insensitive Regex to match school names with accents and other diacriticsRuby 超级不敏感的正则表达式将学校名称与口音和其他变音符号匹配
【发布时间】:2016-09-01 23:17:17
【问题描述】:

这个问题已经在其他编程语言中提出过,但是您将如何在 Ruby 上执行不区分重音的正则表达式?

我当前的代码类似于

scope :by_registered_name, ->(regex){
  where(:name => /#{Regexp.escape(regex)}/i)
}

我想也许我可以用点替换非字母数字+空白字符,并删除escape,但是没有更好的方法吗?如果我这样做,恐怕我会抓住奇怪的东西......

我现在的目标是法语,但如果我也可以为其他语言修复它,那会很酷。

如果有帮助的话,我正在使用 Ruby 2.3。


我意识到我的要求实际上有点强,我还需要捕捉破折号之类的东西。我基本上是在导入一个学校数据库(URL here,标签是<nom>),我希望人们成为能够通过键入其名称找到他们的学校。搜索查询和搜索请求都可能包含重音符号,我相信最简单的方法是让“两者”不敏感。

  • “Télécom”应与“Telecom”匹配
  • “établissement”应与“etablissement”匹配
  • “Institut supérieur national de l'artisanat - Chambre de métiers et de l'Artisanat en Moselle”应与“artisanat chambre de métiers”匹配
  • “Ecole hôtelière d'Avignon (CCI du Vaucluse)”应与 Ecole hoteliere d'avignon 匹配(括号内可以跳过)
  • “Ecole française d'hôtesses”应与“ecole francaise d'hot”匹配

我在那个数据库中发现了一些疯狂的东西,我认为我会考虑清理这个输入

  • “Académie internationale de management - Hotel & Tourism Management Academy”应与“Hotel Tourism”匹配(注意 & 实际上写在 XML 中的 &

【问题讨论】:

  • 您能否编辑您的问题以包括几个您想要处理的输入类型的示例以及相应的结果应该是什么?
  • 在某些语言中,“a”和“å”之间存在巨大差异。法语基本上无动于衷。您有排序偏好吗?
  • 哎呀,我之前并没有真正看过数据库(真遗憾……在我上完所有的数据科学课程之后 ;'( ),但事实证明我还有更多特殊字符要处理,请参阅我的编辑。
  • @CyrilDuchon-Doris 我删除了它,因为我认为它不能回答问题。它解释了如何从正则表达式中删除变音符号,但生成的正则表达式不会匹配带有变音符号的字符串,这是我认为 OP 正在尝试做的。

标签: ruby regex mongodb ruby-on-rails-5


【解决方案1】:

看来 MongoDB 的解决方案是使用text index,即diacritic insensitive。法语是supported

我上次使用 MongoDB 已经有很长时间了,但如果您使用的是 Mongoid,我认为您会在模型中创建一个 text 索引,如下所示:

index(name: "text")

...然后像这样搜索:

scope :by_registered_name, ->(str) {
  where(:$text => { :$search => str })
}

有关更多信息,请参阅 $text query operator 的文档。

原始(错误)答案

事实证明,我是在向后考虑这个问题,并最初写了这个答案。我保留它,因为它可能仍然派上用场。如果您使用的数据库不提供这种功能(就像 MongoDB 那样),一种可能的解决方法是使用以下技术将经过清理的名称与数据库中的原始名称一起存储,并且然后同样清理查询。

由于您使用的是 Rails,您可以使用方便的ActiveSupport::Inflector.transliterate

regex = /aäoöuü/
transliterated = ActiveSupport::Inflector.transliterate(regex.source, '\?')
# => "aaoouu"
new_regex = Regexp.new(transliterated)
# => /aaoouu/

或者简单地说:

Regexp.new(ActiveSupport::Inflector.transliterate(regex.source, '\?'))

您会注意到我提供了'\?' 作为第二个参数,它是用于替换任何无效UTF-8 字符的替换字符串。这是因为默认替换字符串是"?",正如您所知,它在正则表达式中具有特殊含义。

还要注意ActiveSupport::Inflector.transliterate 比类似的I18n.transliterate 做得更多。这是它的来源:

def transliterate(string, replacement = "?")
  I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
    ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
      :replacement => replacement)
end

最里面的方法调用 ActiveSupport::Multibyte::Unicode.tidy_bytes 清除所有无效的 UTF-8 字符。

更重要的是,ActiveSupport::Multibyte::Unicode.normalize“规范化”了字符。例如, 看起来像一个字符,但实际上是两个:拉丁小写字母 E 和组合循环重音。调用I18n.transliterate("ê") 将产生e?,这可能不是您想要的,因此调用normalize 转换为ê,这只是一个字符:带有圆形的拉丁小写字母E。在(前者)上调用I18n.transliterate 将产生e?,这可能不是您想要的,因此normalizetransliterate 之前的步骤很重要。 (如果您对它的工作原理感兴趣,请阅读Unicode equivalence and normalization。)

【讨论】:

  • 感谢您的回答质量。我很欣赏文档的链接并返回编辑您的答案。我想那些 47k 代表是当之无愧的。
  • 抱歉等待,这很完美,而且来自 MongoDB 的文本索引的插值更强(并且对拼写错误有一定的容忍度),这非常适合我的用例。已接受答案。
猜你喜欢
  • 2016-06-17
  • 2014-05-08
  • 2011-05-14
  • 2016-05-03
  • 2011-10-03
  • 2016-09-09
  • 1970-01-01
  • 2020-03-23
  • 1970-01-01
相关资源
最近更新 更多