【问题标题】:Regex-matching a dictionary efficiently in Python在 Python 中有效地匹配字典的正则表达式
【发布时间】:2016-11-22 11:44:50
【问题描述】:

我的 Python 脚本中有一个 word:count 对字典。从这本字典中,我想提取匹配字符串列表中任何项目的条目。

我找到了一个使用正则表达式的有效解决方案(见下文),但它需要永远(运行约 10 小时)。我觉得必须有更快的方法来做到这一点 - 你们对如何改进我的代码有任何意见/想法吗?

import re

dicti={'the':20, 'a':10, 'over':2}
regex_list=['the', 'an?'] 

extractddicti= {k:v for k,v in dicti.items() if any (re.match("^"+regex+"$",k) for regex in regex_list)} 

实际上,字典有大约 60,000 个条目,regex_list 约为 1,000。正则表达式列表中的项目是正则表达式字符串,即包含特殊字符如 ?,括号如 (a|b|c) 等。它们可能匹配多个条目字典。

更新/编辑

(查看已接受的答案以更好地实现相同的想法)

按照 Keozon 和其他人的建议,我首先像这样编译了我的正则表达式:

regex_list=['the', 'an?']
regex_list_compiled=[re.compile("^"+i+"$") for i in regex_list]

然后稍微调整了我的搜索功能:

extractddicti= {k:v for k,v in dicti.items() if any (re.match(regex,k) for regex in regex_list_compiled)} 

性能差异非常惊人:使用包含 14800 个项目的字典和包含 1,100 个正则表达式的列表进行的测试运行需要 34 分钟(不编译),而编译则只用了不到一 (!) 分钟。没想到会如此戏剧化。感谢您的所有帮助!

【问题讨论】:

  • 在您的完整版中,您是否正在编译列表中的正则表达式?如果没有,请这样做。这就是存在编译选项的确切原因。事实上,每个正则表达式在每次与字符串进行比较时都会被编译。 docs.python.org/2/library/re.html#re.compile
  • 使用dicti.iteritems() 会提供一个小的改进(除非这是python3)。但更重要的是,确保您的正则表达式不会受到 catastrophic backtracking 的影响。
  • 我将继续假设您的 real 正则表达式更复杂。因为使用正则表达式搜索“the”可能不是最快的方法。 IE。如果您正在搜索简单的字符串,请使用 str.find
  • 另外,请确保您在正则表达式定义中使用原始字符串文字,否则您可能会遇到令人惊讶的行为(r'the' 而不是'the'
  • re.match 表示字符串的开头,因此^ 是不必要的(除非它是多行的)。如果你使用 Python 3,你可以考虑re.fullmatch

标签: python regex list dictionary list-comprehension


【解决方案1】:

编译一次正则表达式而不是每次使用它们可能会给你带来相当多的性能提升。所以你会有类似的东西:

import re

dicti={'the':20, 'a':10, 'over':2}
patterns=['the', 'an?'] 
regex_matches = [re.compile("^"+pattern+"$").match for pattern in patterns]

extractddicti= {k:v for k,v in dicti.items()
                if any (regex_match(k) for regex_match in regex_matches)} 

【讨论】:

  • 另外,为了提高速度,您可以在推导式中调用regex_match = regex.match,然后调用regex_match。这样做通常是一个微优化,但如果你有一个大循环,它会有所帮助。
  • 谢谢@SethMMorton - 很好,我已经根据你的建议更新了我的答案。
【解决方案2】:

如何将 dict.keys 加入单个字符串以减少循环次数?这里似乎更快:

import re, random, string

#Generate random dictionary
dicti={''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(5)):i for i in range(1000000)}

#Using ; as separator, you can find any match with [^;]
regex_list=['TH[^;]*', 'A[^;]*'] 
joined_keys = ';' + ';'.join(dicti.keys()) + ';'

extractddicti = {i[1:-1]:dicti[i[1:-1]] for sublist in 
                [re.findall(';'+k+';', joined_keys) for k in regex_list] 
                for i in sublist}

10 个循环的 Timeit 结果:

╔════════════╦════════════════════╗
║ Algorithm  ║     Time (sec)     ║
╠════════════╬════════════════════╣
║ Mine       ║ 2.116118321594911  ║
║ E. Gordon  ║ 20.956314717412653 ║
╚════════════╩════════════════════╝

更新

作为@E。 Gordon 建议您应该将分隔符更改为换行符\n,然后您就可以在re.MULTILINE 中使用 ^ 和 $ 特殊运算符。

regex_list=['.*TH', 'AN.*'] 

joined_keys = '\n'.join(dicti.keys())   
all_regex = "^" + "$|^".join(regex_list) + "$" 
matched_keys = re.findall(all_regex, joined_keys, re.MULTILINE)

dicti_match = {k:dicti[k] for k in matched_keys}

【讨论】:

  • 如果你把键连接起来,那么你会得到错误的匹配——例如如果你将字符串 'th' 和 'e' 连接起来,那么你会得到一个匹配 'the' 的结果。不过,这可能是一个有趣的思路 - 也许如果您将键与换行符或其他适当的分隔符连接起来?
  • @caiohamamura 感谢您的意见;我在这里选择了 E. Gordon 的答案,因为它更早且更易于实施,但如果我需要更快的速度,稍后会尝试你的答案。谢谢!
【解决方案3】:

您的问题看起来很像广告屏蔽问题。 Easylist 类似于您的正则表达式列表,但包括 url 和 url 模式。

一般来说正则表达式匹配比较快。然而,正如您已经看到的,它不如不运行它快,常见的正则表达式库也像您的 not optimized for large disjunctions

您的正则表达式词典还包含完整的单词。 Trie 或者,如果内存有问题,DAWG 将非常适合匹配正则表达式字典的完整单词部分。

剩下的问题是将大量输入与大量正则表达式匹配,并且预过滤方法在这种情况下非常有效:首先检查是否存在合理匹配,并且仅当存在合理匹配时才运行完整的正则表达式匹配。即为了'an?要匹配的正则表达式,字符串中必须包含“an”。 Suffix trees 非常适合搜索此类子字符串。如果您的正则表达式总是完全匹配,您可能想要添加一个字符串开始和字符串结束标记。当然,您也可以构建一组固定字符串的 trie 或 dawg 并在每个字符处开始新的搜索。 can be implemented more efficiently 使用强大的自动机库的确定性有限状态自动机具有相同的效果。

希望这种方法可以消除对大部分输入运行正则表达式匹配的需要。一旦你必须运行正则表达式匹配,你可以在其他答案中使用 python 正则表达式实现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-01
    • 2016-12-15
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    相关资源
    最近更新 更多