【问题标题】:Optimizing regular expression techniques优化正则表达式技术
【发布时间】:2015-12-09 01:41:48
【问题描述】:

我想知道正则表达式的优化技术

所以我试图从一个 400k 行的语料库中解析出每个钱的实例。我还需要包括诸如"$10,999.04""one billion and six hundred twenty five thousand dollars" 之类的行以及介于两者之间的所有内容。这需要一个非常冗长的正则表达式,其中包含多个组实例,例如

MONEYEXPRESSION = '(?:\d\d?\d?(?:,?\d{3})*(?:\.\d+)?)'
(one|two|...|ninety[\s-]?nine|hundred|a hundred|MONEYEXPRESSION)((\s*and\s*|\s*-\s*|\s*)(one|two|...|ninety[\s-]?nine|hundred|a hundred|MONEYEXPRESSION))*

更重要的是,为了要求它是金钱的一个实例并避免匹配诸如"five hundred people were at the event" 之类的行,我有 4 个 OR'd 选项要求 "$", "dollars?", or "cents?" 至少在句子中的特定位置一次。

正则表达式差不多有 20k 个字符! :(

您可以想象,如果使用如此广泛的表达方式,以及任何不良做法,它确实会增加时间。过去 2 小时我一直在语料库上运行它,但仍然没有完成匹配。我想知道优化和修剪不必要的正则表达式的一些最佳实践是什么。我正在使用的操作很昂贵,可以补充更好的操作。如果可能有更好的方法来解决这个问题?

【问题讨论】:

  • 也许您应该编写一个真正的解析器,而不是尝试对所有内容都使用正则表达式。
  • 相信我,我想这样做,但这是我需要使用正则表达式的任务。这就是为什么我不是在寻求解决方案,而是寻求使其合理运行或最小化我已经编写的内容的技术
  • PCRE 支持递归,因此对于 100 以下的数字不必重复表达式,您可以定义一次,然后在 hundredthousand 之前引用它。
  • 您是否预先编译一次正则表达式,然后使用编译后的版本?另外,您是否允许将其拆分为多个正则表达式模式,以便您可以先测试更频繁的部分并避免测试每一行的每个部分?
  • @ehaymore 我正在编译一次,我可以将其拆分为多个模式

标签: python regex parsing optimization


【解决方案1】:

我会尝试做一些类似的事情:

keywords = ["$","dollar","dollars","cent","cents"]
my_file = r"c:\file.txt"
output = r"c:\output.txt"
filtered_lines = []
with open(my_file,"r") as f:
     for line in f:
         for k in keywords:
             if k in line:
                filtered_lines.append(line)
                break
with open(output,"w") as o:
    o.write("\n".join(filtered_lines))

【讨论】:

  • 此解决方案需要正则表达式。另外,这不是一行一行的。我仍然需要能够匹配“我的帐户中有 [10 亿\n美元]”
【解决方案2】:

我会将数字和写出的正则表达式分开,分两步完成,首先提取数字量(这是简单的部分),然后再做写出的数量。

写出的部分最有问题的是,如果你有one hundred people,它会尝试所有的数十亿和数千,以及one这个词上已经存在的所有内容,最后发现没有dollars。但更糟糕的是,它会再次尝试所有单词 hundred,然后是 people...
因此,理想情况下,它会从后面开始,因此它不会尝试将所有内容与每个单词匹配,而只匹配“美元”、“美分”或其他任何内容,然后才进行昂贵的部分。

因此,如果可能,请尝试将您的文件从头到尾匹配以获取已写出的内容。肯定会很难理解这一点,但我敢打赌它会明显更快。
如果不可能,我希望你现在至少知道主要瓶颈在哪里。

啊,一些单词边界也可能有助于将匹配从在每个字符减少到在每个单词的开头......我没有提到它上面,但实际上对于这个例子,引擎在 'o' 开始匹配,然后在 'n'、'e'、'' 等处开始匹配。

【讨论】:

    【解决方案3】:

    您要问的是优化性能,所以让我们专注于此。使正则表达式引擎真正缓慢的是backtracking,导致回溯的原因是可能在字符串中的不同位置成功的部分,没有明确的方法来决定。所以试试这些经验法则:

    1. 来自上面的回溯链接:“嵌套重复运算符时,请绝对确保只有一种方法可以匹配相同的匹配项。”

    2. 避免使用大型可选组件。而不是像(<number>? <number>)? <number> 这样的东西来匹配带有空格分隔元素的序列,而是写(<number> ?)+

    3. 避免使用空字符串可以满足的组件——引擎将尝试在每个位置满足它们。

    4. 确保正则表达式中不受约束的部分在长度上是有界的,尤其是在后面的部分不能被可靠识别的情况下。像A.*B? 这样的东西是自找麻烦——这可以匹配以A 开头的任何东西

    5. 不要使用前瞻/后瞻。几乎总是有更简单的方法。

    更一般地说,保持简单。我不确定您是如何为这项任务获得 20K 个字符的,但我敢打赌,有一些方法可以简化它。一个考虑因素是可以匹配您无论如何都不会看到的东西。

    例如,为什么要匹配从 1 到 99 的所有数字,而不仅仅是它们的组成部分?是的,你会匹配诸如“九九十美元”之类的废话,但这并没有什么坏处。 您正在搜索金额,而不是验证输入。 例如,这应该匹配所有写出的少于一百万美元的美元金额:

    ((one|two|three|...|twenty|thirty|...|ninety|hundred|thousand|and) ?)+ (dollars?|euros?)\b
    

    由于这被标记为“python”,这里还有两个建议:

    1. 如果任务(或分配)允许您按步骤分解搜索,请执行此操作。 一个能做所有事情的正则表达式通常必须非常复杂,以至于它比简单地按顺序运行多个搜索要慢。

    2. 即使您被限制使用一个怪物正则表达式,也可以将其分段编写并使用 python 将其组装成一个字符串。执行时不会有任何区别,但使用起来会容易得多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多