给定一个字符串,如何遍历许多正则表达式以找到匹配?

时间:2011-01-19 01:22:23

标签: ruby regex string

给定一个字符串,以及正则表达式到整数的映射,我想找出字符串映射到的整数(假设该字符串将恰好匹配其中一个正则表达式)

只是遍历正则表达式的散列,尝试每个正则表达式对字符串,然后输出值是否效率低?当然我不能明确枚举所有可能的string =>整数映射,但尝试在一堆正则表达式中匹配每个正则表达式似乎很糟糕。

4 个答案:

答案 0 :(得分:1)

RegexpTrie,当我上次寻找类似的东西时不在身边,有助于解决这类问题:

require 'regexp_trie'

sentence = 'life on the mississippi'
words_ary = %w[the sip life]

words_regex = /\b(?:#{RegexpTrie.union(words_ary, option: Regexp::IGNORECASE).source})\b/i 
# => /\b(?:(?:the|sip|life))\b/i

words_to_ints = words_ary.each_with_index.to_h
# => {"the"=>0, "sip"=>1, "life"=>2}

sentence_words = sentence.split
# => ["life", "on", "the", "mississippi"]

word_hits = sentence_words.map { |w| w[words_regex] }
# => ["life", nil, "the", nil]

nil表示正则表达式中没有该单词匹配。

words_to_ints.values_at(*word_hits)
# => [2, nil, 0, nil]

同样,nil表示没有匹配。使用以下内容可以忽略nil个值:

word_hits = sentence_words.map { |w| w[words_regex] }.compact
# => ["life", "the"]

words_to_ints.values_at(*word_hits)
# => [2, 0]

同样,如果您想扫描单词匹配而不是单个单词的句子:

require 'regexp_trie'

sentence = 'life on the mississippi'
words = %w[the sip life]

words_regex = /\b(?:#{RegexpTrie.union(words, option: Regexp::IGNORECASE).source})\b/i 
# => /\b(?:(?:the|sip|life))\b/i

words_to_ints = words.each_with_index.to_h
# => {"the"=>0, "sip"=>1, "life"=>2}

word_hits = sentence.scan(words_regex)
# => ["life", "the"]

words_to_ints.values_at(*word_hits)
# => [2, 0]

Perl为这类名为Regexp::Assemble的东西提供了一个非常有用的模块,它允许你将正则表达式组合成一个大的,然后搜索字符串,返回命中。如果你想知道,你可以让它告诉你使用了哪种模式。

Ruby没有这样的模块,但这有点接近:

patterns = {
  /(foo)/ => 1,
  /(bar)/ => 2
}

pattern_union = Regexp.union(patterns.keys)

pattern_union # => /(?-mix:(foo))|(?-mix:(bar))/

str = 'foo some text'

if (pattern_union =~ str)

  # these show what are being processed...
  pattern_union.match(str).captures # => ["foo", nil]
  pattern_union.match(str).captures.zip(patterns.keys).find_all{ |c| c[0] }.map{ |c| c[1] } # => [/(foo)/]

  # process it...
  matched_pattern_values = patterns.values_at(*pattern_union.match(str).captures.zip(patterns.keys).find_all{ |c| c[0] }.map{ |c| c[1] })

  # here's what we got
  matched_pattern_values # => [1]

end

可能有一种方法可以在一行中完成,但这很有效。

我认为重要的是避免必须迭代模式以在可能的情况下查找字符串中的命中,因为随着文本大小或模式数量的增加,它们会变慢。

有关从Ruby使用Regexp :: Assemble的更多信息,请参阅“Is there an efficient way to perform hundreds of text substitutions in Ruby?”。

答案 1 :(得分:1)

按照你的建议,循环遍历正则表达式/数字的哈希并返回匹配字符串的第一个:

def find_match(regex_mapping, str)
  regex_mapping.each do |regex, n|
    return n if str =~ regex
  end
  return nil
end

关于效率的唯一要说的是:无论如何它可能无关紧要。只需尽可能清晰简单地编写代码,然后,最后,如果要慢,请通过分析器运行它(例如绝对令人敬畏的perftools.rb)并查看热点是什么。优化那些。在编写任何代码之前不要进行优化。

也就是说,在这种情况下你可以做的一个简单的优化,不需要花费任何成本,就是将正则表达式按顺序放入映射哈希中,以便最有可能匹配,这样就可以减少比较必须要做(但这是一个概率优化,最坏的情况下运行时间保持不变)。这仅适用于Ruby 1.9,因为哈希不会在1.8中保留其插入顺序。

答案 2 :(得分:0)

这取决于你的正则表达的复杂程度。如果你可以将它们放在捕获块中并让捕获块映射回你需要的整数,那么你应该没问题。

例如:

(is).*(test)

有两个捕获块。这将匹配:

This is a test

捕获将为1:is2:test

您可以在http://www.rubular.com/

上快速试用

答案 3 :(得分:-1)

你说'看起来很糟糕',但最后,你可能无能为力:你必须将每个字符串与一系列正则表达式匹配,直到匹配为止。您可以记住结果并以其他方式变得聪明,例如“如果此正则表达式失败,其他10个也将失败”,但这些都是您可能不需要的性能优化。

最简单的优化可能是创建具有共享特征的正则表达式组,并首先测试字符串所在的组。如果string =〜/ ^ a /是nil,则测试以don'开头的字符串的所有其他正则表达式需要再进行测试。