【问题标题】:What is the most efficient way to search a blob of text for an array of regular expressions?在文本块中搜索正则表达式数组的最有效方法是什么?
【发布时间】:2011-08-05 16:03:22
【问题描述】:

我正在寻找最有效的方法来搜索存储在数组中的许多正则表达式的文本块 (± 1/2KB)。

示例代码:

patterns = [/patternA/i,/patternB/i,/patternC/m,...,/patternN/i]

content  = "Lorem ipsum dolor sit amet, consectetur... officiam id est laborum."

r = patterns.collect{ |pattern|

  pattern unless ( content =~ pattern ).blank?

}.compact

r 现在包含与 content 字符串匹配的模式。

【问题讨论】:

标签: ruby regex arrays search performance


【解决方案1】:

如果您只对任何模式是否与文本匹配感兴趣,请考虑将所有模式组合成一个大正则表达式,使用正则表达式“或”运算符,然后编译该大正则表达式一次。

例如,如果您的模式是:ABC,则创建一个 A|B|C 形式的正则表达式

对不起,我不懂 Ruby,但希望你能把它变成代码(:

旁注:这是我上次查看 Mercurial 的 .hgignore 文件的处理方式。在这种情况下,一个大的正则表达式会抛出 1000 个文件名,这比在数百个较小的正则表达式中抛出的文件名更有效。

【讨论】:

  • 我也想过。为此,您可以使用Regexp.union(A, B, C)。但问题似乎是要得到哪一个匹配。
  • 是的,我需要找到匹配的模式。考虑一个包含 pattern 属性的表达式模型,该属性是一个正则表达式。 Expression.all.select{ |expression| content =~ expression.pattern } => [<#Expression>, <#Expression>, ..., <#Expression>] 即其 pattern 属性与 content 匹配的表达式列表。所以我不能在这里真正使用 j.w. 的解决方案(即合并模式并构建一个巨大的正则表达式。)
【解决方案2】:

解决方案 1

这样做:

r = patterns.select{|pattern| content =~ pattern}

由于字符串很大,最好在String 上实现这个方法,而不是在其他东西上,因为传递一个大参数似乎很慢。

class String
  def filter_patterns patterns
    patterns.select{|r| self =~ pattern}
  end
end

并像这样使用它:

content.filter_patterns(patterns)

解决方案 2

限制每个正则表达式不包含命名/编号的捕获。

combined_regex = Regexp.new(patterns.map{|r| "(?=[.\n]*(#{r.source}))?"}.join)
content =~ combined_regex

如果patterns 中的正则表达式包含命名/编号捕获,则以下部分将出现问题。如果有办法知道每个正则表达式有多少潜在捕获,那么它将解决问题。

r = patterns.select.with_index{|pattern, i| Regexp.last_match[i]}

加法

给定:

dogs = {
  'saluki' => 'Hounds',
  'russian wolfhound' => 'Hounds',
  'italian greyhound' => 'Hounds',
   ..
}
content = "Running in the fields at great speeds, the sleek saluki dog comes from..."

你可以这样做:

combined_regex =
    Regexp.new(dogs.keys.map{|w| "(?=[.\n]*(#{w}))?"}.join, Regexp::IGNORECASE)
content =~ combined_regex
r = patterns.select.with_index{|pattern, i| Regexp.last_match[i]}
"This article talks about #{r.collect{|x| dogs[x]}.to_sentence}."
=> "This article talks about Hounds."

为避免像This article talks about Hounds, Hounds and Hounds. 这样的输出,您可能需要将uniq 放入其中。

"This article talks about #{r.uniq.collect{|x| dogs[x]}.to_sentence}."

【讨论】:

  • 嗨,Sawa,感谢您的快速回复!你能解释一下是什么让这更有效吗?不检查 nil (或者更确切地说是空白?这是一组测试)?并使用 select 而不是 collect 来避免结果数组中的 nil 值需要应用“紧凑”方法?
  • select 是有效的,因为它只允许非零模式通过,因此您不必使用compact 过滤它。我没有使用empty?,因为我假设您的模式不包括与空字符串匹配的模式。是吗?
  • 不,它不包含与空字符串匹配的模式。
  • 解决方案 #2 对我不起作用,原因是我对 j.w. 的回答的评论中提到的原因。
  • “传递一个大参数似乎很慢”?你有这个说法的依据吗?
【解决方案3】:

怎么样:

text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor magna'
targets = [ /(am?et)/, /(ips.m)/, /(elit)/, /(magna)/, /([Ll]or[eu]m)/ ]

regex = Regexp.union(targets)

hits = []
text.scan(regex) { |a| hits += a.each_with_index.to_a }
r = hits.select{ |w,i| w }.map{ |w,i| targets[i]} # => [/([lL]or[eu]m)/, /(ips.m)/, /(am?et)/, /(elit)/, /(magna)/]

这可以按照在文本中找到单词的顺序返回匹配的模式。

也可能有一种使用命名捕获的方法。

【讨论】:

  • 我尝试了union(如我之前回答的编辑),但意识到一旦匹配成功,它就会停在那里,并且看不到替代正则表达式是否匹配。
  • 通常,当满足所有条件时,正则表达式是快乐的。使用联合,如果任何 | 连接模式匹配,因为它们是 or'd 在一起。 Scan 通过遍历字符串查找所有匹配项,每次都重试该模式。如果正则表达式联合的不同部分命中,您仍然会看到它。
  • 我明白了。您的union 有效,因为正则表达式是(可能是互斥的)单词/短语,如提问者在评论中提供的链接页面中给出的那样。 (当第一次提出这个问题时,还不清楚;我不得不考虑多个正则表达式可能匹配字符串的同一部分的可能性。在这种情况下,union 不能使用。)跨度>
【解决方案4】:

您想要的正是词法分析器的设计目的。从输入流中挑选出一组正则表达式,只需一次通过所需的输入。

不幸的是,我无法为 Ruby 找到一个好的词法分析器 gem,它可以让您定义自己的词法分析器。如果我发现任何东西,我会更新答案。

【讨论】:

    猜你喜欢
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    • 2012-03-24
    • 2012-02-12
    • 2016-06-06
    • 2014-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多