【问题标题】:Optimising ruby regexp -- lots of match groups优化 ruby​​ 正则表达式——大量匹配组
【发布时间】:2012-11-07 09:00:29
【问题描述】:

我正在开发一个 ruby​​ 基础词法分析器。为了提高性能,我将所有标记的正则表达式加入到一个带有匹配组名称的大正则表达式中。生成的正则表达式如下所示:

/\A(?<__anonymous_-1038694222803470993>(?-mix:\n+))|\A(?<__anonymous_-1394418499721420065>(?-mix:\/\/[\A\n]*))|\A(?<__anonymous_3077187815313752157>(?-mix:include\s+"[\A"]+"))|\A(?<LET>(?-mix:let\s))|\A(?<IN>(?-mix:in\s))|\A(?<CLASS>(?-mix:class\s))|\A(?<DEF>(?-mix:def\s))|\A(?<DEFM>(?-mix:defm\s))|\A(?<MULTICLASS>(?-mix:multiclass\s))|\A(?<FUNCNAME>(?-mix:![a-zA-Z_][a-zA-Z0-9_]*))|\A(?<ID>(?-mix:[a-zA-Z_][a-zA-Z0-9_]*))|\A(?<STRING>(?-mix:"[\A"]*"))|\A(?<NUMBER>(?-mix:[0-9]+))/

我将它与生成 MatchData 的字符串进行匹配,其中只解析了一个标记:

bigregex =~ "\n ... garbage"
puts $~.inspect

哪些输出

#<MatchData
 "\n"
 __anonymous_-1038694222803470993:"\n"
 __anonymous_-1394418499721420065:nil
 __anonymous_3077187815313752157:nil
 LET:nil
 IN:nil
 CLASS:nil
 DEF:nil
 DEFM:nil
 MULTICLASS:nil
 FUNCNAME:nil
 ID:nil
 STRING:nil
 NUMBER:nil>

所以,正则表达式实际上匹配了“\n”部分。现在,我需要确定它所属的匹配组(从 #inspect 输出中可以清楚地看到它是 _anonymous-1038694222803470993,但我需要以编程方式获取它)。

除了迭代 #names 之外,我找不到任何选项:

m.names.each do |n|
  if m[n]
    type = n.to_sym
    resolved_type = (n.start_with?('__anonymous_') ? nil : type)
    val = m[n]
    break
  end
end

验证匹配组确实有匹配项。

这里的问题是它很慢(我在循环中花费了大约 10% 的时间;还有 8% 的时间在获取 @input[@pos..-1] 以确保 \A 按预期工作以匹配字符串(我不丢弃输入,只是移动其中的@pos)。

您可以在GH repo查看完整代码。

关于如何使它至少更快一点的任何想法?是否有任何选项可以更轻松地确定“成功”匹配组?

【问题讨论】:

  • 非常好,但是使用 RE 进行词法分析器使得开发变得非常简单(节省了开发时间)。不过,我正在考虑一种更直接的词法分析器方法。

标签: ruby regex optimization pattern-matching lexer


【解决方案1】:

您可以使用正则表达式方法.captures().names() 来做到这一点:

matching_string = "\n ...garbage"   # or whatever this really is in your code
@input = matching_string.match bigregex   # bigregex = your regex
arr = @input.captures

arr.each_with_index do |value, index|     
  if not value.nil?
    the_name_you_want = @input.names[index]
  end
end

或者,如果您期望多个成功的值,您可以这样做:

success_names_arr = []
success_names_arr.push(@input.names[index]) #within the above loop

与您最初的想法非常相似,但如果您正在寻找效率,.captures() 方法应该会有所帮助。

【讨论】:

  • 当然。但那时我不知道,组合正则表达式的哪一部分匹配(即原始令牌名称是什么)。
  • 那么也许尝试使用pre_match? a = @input.match(/your_regex/).pre_match。如果我正确理解您的代码,那么 pre_match 就是名称? (您可能需要 gsub 去掉冒号)。
  • 在一个稍微单独的注释中,您可以在您的代码中将其干燥一点(第 129-131 行)val, type, resolved_type = nil
  • 更新了问题,详细说明了我从哪里获取匹配数据以及我错过了什么
  • 好的,添加的信息很有帮助。我在上面更新了我的答案^^。这应该适合你
【解决方案2】:

我可能完全误解了这一点,但我假设除了一个令牌之外的所有令牌都不是 nil,而这就是你想要的那个?

如果是这样,那么根据您使用的正则表达式的风格,您可以使用负前瞻来检查非nil

([^\n:]+:(?!nil)[^\n\>]+)

这将匹配整个令牌,即NAME:value

【讨论】:

  • 只有一个 token 不是 nil,但我需要弄清楚是哪一个。正则表达式是每个令牌正则表达式的串联,以加快速度。
  • 我对 ruby​​ 不太了解,所以实现可能需要调整,但上面的正则表达式将匹配一个包含 : 的字符串,后面没有 nil。然后,您可以根据冒号拆分捕获的字符串。我还修改了正则表达式以考虑关闭&gt;
  • 让我把它弄对,你建议正则表达式匹配 MatchData#inspect ?
  • 我不确定这是什么意思
猜你喜欢
  • 1970-01-01
  • 2017-03-17
  • 1970-01-01
  • 1970-01-01
  • 2011-07-14
  • 2011-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多