用正则表达式缓慢解析

时间:2011-05-27 17:14:10

标签: ruby regex parsing jruby token

我有以下用于数据验证的正则表达式:

lexer = /(?:
      (.{18}|(?:.*)(?=\s\S{2,})|(?:[^\s+]\s){1,})\s*
      (.{18}|(?:.*)(?=\s\S{2,})|(?:[^\s+]\s){1,})\s*
      (?:\s+([A-Za-z][A-Za-z0-9]{2}(?=\s))|(\s+))\s*
      (Z(?:RO[A-DHJ]|EQ[A-C]|HIB|PRO|PRP|RMA)|H(?:IB[2E]|ALB)|F(?:ER[2T]|LUP2|ST4Q))\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\s+\d{10}|\s+)\s*
      (\d{6})\s*
      (.*)(?=((?:\d{2}\/){2}\d{4}))\s*
      ((?:\d{2}\/){2}\d{4})\s*
      (\S+)
    )/x

问题是我必须遍历10000行(平均值)的文件,使用正则表达式执行验证,导致解析缓慢的应用程序。

  filename = File.new(@file, "r")
  filename.each_line.with_index do |line, index|
    next if index < INFO_AT + 1

    lexer = /(?:
      (.{18}|(?:.*)(?=\s\S{2,})|(?:[^\s+]\s){1,})\s*
      (.{18}|(?:.*)(?=\s\S{2,})|(?:[^\s+]\s){1,})\s*
      (?:\s+([A-Za-z][A-Za-z0-9]{2}(?=\s))|(\s+))\s*
      (Z(?:RO[A-DHJ]|EQ[A-C]|HIB|PRO|PRP|RMA)|H(?:IB[2E]|ALB)|F(?:ER[2T]|LUP2|ST4Q))\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\S+)\s*
      (\s+\d{10}|\s+)\s*
      (\d{6})\s*
      (.*)(?=((?:\d{2}\/){2}\d{4}))\s*
      ((?:\d{2}\/){2}\d{4})\s*
      (\S+)
    )/x

    m = lexer.match(line)
    begin
      if (m) then ...

修改 在这里,您可以找到我需要解析的一些行:File

编辑II @Mike R

我正在解析每行包含25列的文件,每列可能都有自己的验证方式。它可以是空白也可以是完整的字符集。

  • 验证是必需的,因为我必须删除与那种部分不匹配的行。
  • 可能没有必要
  • 这是必要的

我不相信它的构造很糟糕,它使用的前瞻,可能在我重复代码的部分(我只是不记得捕获组索引\ 1 ... \ n,如果这样是你的意思!)我也相信这里发生了灾难性的回溯。

如果您看到该文件,也许您会明白为什么我这样做!我们以第一列为例。我必须匹配“部件号”,我没有任何关于如何执行此操作的规则,例如:

  • 123456789
  • 1 555 989
  • 0123456789123456789

简单的\ S +或(\ S + \ s){1,}都无法解决此问题,因为我不会保证数据的完整性。

泰!

有任何改进,建议吗?

~EderQuiñones

4 个答案:

答案 0 :(得分:5)

  1. 正则表达式在循环中不会发生变化,因此请将lexer =分配拉出循环。 (“循环不变代码运动。”)

  2. 我不确定这会更快或更慢,但重复的子表达式可能只是一个外部重复组。

  3. 所以这很明显,我敢肯定,但编写一个真正的解析器可能是有意义的。 Ruby有一个名为Treetop的高级PEG解析器生成器。传统方法是Lex + Yacc(例如flex + bison)或者只是实现扫描程序和递归下降解析器的C程序。老学校,是的,但很容易写,并且从另一个CRUD计划中变得有趣。

  4. 我同意其他响应者的观点,表达是不必要的多余,复杂,应该分解为至少两个单独的和更小的。两个表达式可能很有趣,因为它们可以相反地锚定。

答案 1 :(得分:2)

你可能会得到catastrophic backtracking

即,但不仅仅是因为你贪婪的\S+\s*序列。他们也可能是无效的,因为:

(\S+)\s*
(\S+)\s*
(\S+)\s*
(\S+)\s*

将匹配'abcd'(在痛苦后退量之后)。

另外,正如在另一个答案中指出的那样,寻求从循环中编译该正则表达式定义。

答案 2 :(得分:1)

你在解析中实际上在寻找什么?

据我所知,在线上只有一些重要元素。

  • 您正在寻找以Z开头的字母数字 (Z(?:RO[A-DHJ]|EQ[A-C]|HIB|PRO|PRP|RMA)|H(?:IB[2E]|ALB)|F(?:ER[2T]|LUP2|ST4Q))\s*

  • 您正在寻找一个6位数字 (\d{6})\s*

  • 您正在寻找约会 (?=((?:\d{2}\/){2}\d{4}))\s*
    ((?:\d{2}\/){2}\d{4})\s*

(请注意,最后一个日期表达式是这个结构有多糟糕的一个例子。它有一个预测日期,紧接着是完全相同日期的非捕获匹配。因此,预测是没有意义的看到关于灾难性回溯的另一个答案,这无疑是在这里发生的。)

该表达式中的其他所有东西都可以匹配任何东西,我发现很难相信它们表达任何有意义形式的规则。 真实数据不会变化那么多,或者它会因一些精确的规则而变化,这些规则可以用一个(或更多的正则表达式)表示。

所以,完全破坏表达式并停止尝试一次完成所有操作。执行单独的操作(根据需要在\ s上拆分线)并将其分解为几个小的正则表达式这实际上与您真正需要验证的内容相匹配。

答案 3 :(得分:1)

您的文件是具有固定宽度字段的格式。 Ruby有一个名为unpack的字符串方法,专门用于解析这种类型的文件。

field_widths = [19,41,14,11,11,11,11,11,11,11] #etc
field_pattern = "A#{fields.join('A')}"

然后在你的行循环中:

row = line.unpack(field_pattern)

现在你有一个数组(行),其中包含每个字段的内容。然后,您可以将正则表达式应用于每个正则表达式以进行验证。这更快,更易于管理,并且还允许特定于字段的错误消息。