让正则表达式尝试 运行 更快?

Getting a regex trie to run faster?

我有一个 50mb 的正则表达式特里树,我用它来拆分短语。

相关代码如下:

import io
import re

with io.open('REGEXES.rx.txt', encoding='latin-1') as myfile:
        regex = myfile.read()


while True == True:
    Password = input("Enter a phrase to be split: ")

    Words = re.findall(regex, Password)

    print(Words)

由于正则表达式太大,这需要很长时间!

这是我现在正在尝试的代码,使用 re.compile(TempRegex):

import io
import re

with io.open('REGEXES.rx.txt', encoding='latin-1') as myfile:
        TempRegex = myfile.read()

regex = re.compile(TempRegex)

while True == True:
    Password = input("Enter a phrase to be split: ")

    Words = re.findall(regex, Password)

    print(Words)

我想做的是检查输入的短语是否是名称的组合。例如,短语 "johnsmith123" 到 return ['john', 'smith', '123']。正则表达式文件是由一个工具根据来自 Facebook 的每个名字和姓氏的单词列表创建的。我想看看输入的短语是否本质上是该单词列表中单词的组合......如果 johns 和 mith 是列表中的名字,那么我想要 "johnsmith123" 到 return ['john', 'smith', '123', 'johns', 'mith']。

如果编译它,正则表达式模式将被编译成字节码,然后 运行 由匹配引擎编译。如果你不编译它,它会在调用时为同一个正则表达式一遍又一遍地加载它。这就是为什么如果您对多个不同的记录使用相同的正则表达式,编译的速度会更快。

Python 的正则表达式引擎实际上不是正则表达式,因为它包括后向、捕获组、反向引用等功能,并使用回溯来匹配最左边的有效分支而不是最长的分支。

如果您使用真正的正则表达式引擎,并且您的正则表达式不需要这些功能,您几乎总能获得更好的结果。

真正的正则表达式最重要的品质之一是它将总是return结果的时间与输入的长度成正比,而无需使用任何内存。

我自己用 C 实现的 DFA 写了一个(但可以通过 cffi 从 python 中使用),它将具有最佳的渐近性能,但我没有尝试过矢量化等常数因子改进和装配一代。我没有制作普遍可用的 API,因为我只需要从我的库中调用它,但从示例中应该不难理解。 (请注意,搜索可以实现为先与 .* 匹配,然后再向后匹配,但出于我的目的,我宁愿 return 单个字符作为错误标记)。 Link to my project

您也可以考虑离线构建 DFA 并将其用于程序的多次运行 - 但这是 flex 所做的,所以我对我的项目这样做没有意义,所以也许只是使用如果您对 C 感到满意?当然,无论如何您几乎肯定必须编写一些自定义 C 代码才能使用我的项目...

我认为正则表达式不是解决问题的方法。在我看来,您要做的就是找到给定字符串中恰好是名称的所有子字符串的列表。

如果用户输入的是密码或口令,则表示字符串相对较短。很容易将该字符串分解为一组可能的子字符串,然后针对包含名称的另一组测试该组。

长度为n的字符串中子串的个数为n(n+1)/2。假设没有人会输入超过 40 个字符,您只会看到 820 个子字符串,其中许多可能会因为太短而被删除。这是一些代码:

def substrings(s, min_length=1):
    for start in range(len(s)):
        for length in range(min_length, len(s)-start+1):
            yield s[start:start+length]

所以问题是将名称加载到 suitable 数据结构中。您的正则表达式为 50MB,但考虑到您在其中一条评论中显示的片段,由于正则表达式语法的开销,实际数据量将比实际数据量小很多。

如果您只是使用每行一个名称的文本文件,您可以这样做:

names = set(word.strip().lower() for word in open('names.txt'))

def substrings(s, min_length=1):
    for start in range(len(s)):
        for length in range(min_length, len(s)-start+1):
            yield s[start:start+length]

s = 'johnsmith123'
print(sorted(names.intersection(substrings(s)))

可能给出输出:

['jo', 'john', 'johns', 'mi', 'smith']

考虑到可能的小数据集,我怀疑是否会出现内存问题,但是如果您发现没有足够的内存来一次加载完整的数据集,您可以考虑使用 sqlite3 和一个简单的table 存储名称。这样查询起来会比较慢,但它适合内存。

另一种方法是使用 shelve 模块创建一个以名称作为键的持久字典。