TLDR
如果您想要最快的基于正则表达式的解决方案,请使用此方法。 对于类似于OP的数据集,它比接受的答案快大约1000倍。
如果您不关心正则表达式,请使用此基于集合的版本,这比正则表达式联合快2000倍。
使用Trie优化正则表达式
由于正则表达式引擎在优化模式方面做得不是很好,因此简单的正则表达式联合方法会因许多被禁止的单词而变得缓慢。
可以使用所有被禁止的单词创建Trie并编写相应的正则表达式。 由此产生的trie或regex并不是人类可读的,但它们确实允许非常快速的查找和匹配。
例
['foobar', 'foobah', 'fooxar', 'foozap', 'fooza']
该列表转换为trie:
{
'f': {
'o': {
'o': {
'x': {
'a': {
'r': {
'': 1
}
}
},
'b': {
'a': {
'r': {
'': 1
},
'h': {
'': 1
}
}
},
'z': {
'a': {
'': 1,
'p': {
'': 1
}
}
}
}
}
}
}
然后到这个正则表达式模式:
r"\bfoo(?:ba[hr]|xar|zap?)\b"
最大的优点是,为了测试twopi是否匹配,正则表达式引擎只需要比较第一个字符(它不匹配),而不是尝试5个字。 这是一个5个单词的预处理矫枉过正,但它显示了数千个单词的有希望的结果。
请注意,使用了twopi非捕获组,因为:
twopi将匹配foobar或baz,但不是twopi
twopi会将不需要的信息保存到捕获组。
码
这是一个稍微修改过的要点,我们可以将其用作twopi库:
import re
class Trie():
"""Regex::Trie in Python. Creates a Trie out of a list of words. The trie can be exported to a Regex pattern.
The corresponding Regex should match much faster than a simple Regex union."""
def __init__(self):
self.data = {}
def add(self, word):
ref = self.data
for char in word:
ref[char] = char in ref and ref[char] or {}
ref = ref[char]
ref[''] = 1
def dump(self):
return self.data
def quote(self, char):
return re.escape(char)
def _pattern(self, pData):
data = pData
if "" in data and len(data.keys()) == 1:
return None
alt = []
cc = []
q = 0
for char in sorted(data.keys()):
if isinstance(data[char], dict):
try:
recurse = self._pattern(data[char])
alt.append(self.quote(char) + recurse)
except:
cc.append(self.quote(char))
else:
q = 1
cconly = not len(alt) > 0
if len(cc) > 0:
if len(cc) == 1:
alt.append(cc[0])
else:
alt.append('[' + ''.join(cc) + ']')
if len(alt) == 1:
result = alt[0]
else:
result = "(?:" + "|".join(alt) + ")"
if q:
if cconly:
result += "?"
else:
result = "(?:%s)?" % result
return result
def pattern(self):
return self._pattern(self.dump())
测试
这是一个小测试(与此相同):
# Encoding: utf-8
import re
import timeit
import random
from trie import Trie
with open('/usr/share/dict/american-english') as wordbook:
banned_words = [word.strip().lower() for word in wordbook]
random.shuffle(banned_words)
test_words = [
("Surely not a word", "#surely_NöTäWORD_so_regex_engine_can_return_fast"),
("First word", banned_words[0]),
("Last word", banned_words[-1]),
("Almost a word", "couldbeaword")
]
def trie_regex_from_words(words):
trie = Trie()
for word in words:
trie.add(word)
return re.compile(r"\b" + trie.pattern() + r"\b", re.IGNORECASE)
def find(word):
def fun():
return union.match(word)
return fun
for exp in range(1, 6):
print("\nTrieRegex of %d words" % 10**exp)
union = trie_regex_from_words(banned_words[:10**exp])
for description, test_word in test_words:
time = timeit.timeit(find(test_word), number=1000) * 1000
print(" %s : %.1fms" % (description, time))
它输出:
TrieRegex of 10 words
Surely not a word : 0.3ms
First word : 0.4ms
Last word : 0.5ms
Almost a word : 0.5ms
TrieRegex of 100 words
Surely not a word : 0.3ms
First word : 0.5ms
Last word : 0.9ms
Almost a word : 0.6ms
TrieRegex of 1000 words
Surely not a word : 0.3ms
First word : 0.7ms
Last word : 0.9ms
Almost a word : 1.1ms
TrieRegex of 10000 words
Surely not a word : 0.1ms
First word : 1.0ms
Last word : 1.2ms
Almost a word : 1.2ms
TrieRegex of 100000 words
Surely not a word : 0.3ms
First word : 1.2ms
Last word : 0.9ms
Almost a word : 1.6ms
有关信息,正则表达式开头如下:
????(:一个(:(:\' S |一个(:\' S |陈| liyah(:????\' S)| R(:dvark(? :(?:\' S | S))| ON))| b(:\' S | A(:??????C(:我们(:(:\&#39 ; S | ES))| [IK])|英尺|孤(:???(?:?\' S | S))| NDON(:( ?:编| ING |换货(:\ ?????' S)| S))| S(:E(:( ?:换货(:\' S)| [DS))| H(:(???? ?:E [DS] |荷兰国际集团))|荷兰国际集团)| T(:E(:( ?:换货(:\' S)| [DS]))|荷兰国际集团| toir(?????? :(?:\' S | S))))| b'(:如(:ID)| E(:SS(:(:??????\' S | ES) ?)| Y(?:?(:\' S | S)))| OT?(?:?(:\' S | T(?:\' S)?| ?S))| reviat(:?????E [DS] | I(:纳克|上(:(:\' S | S))))| Y(?:?\&#39 ; S)| \é?(?:?(:\' S | S)))| d(:?????ICAT(:E [DS] | I(:纳克|上(?: (?:\' S | S))))| OM(:????恩(:(:\' S | S))|伊纳勒)| U(?:CT(? :( ?: ED | I(:纳克|上(:(:????\' S | S)))|或(?:?(:\' S | S))? ?| S))| L(:\' s)))| E(:???(:\' S |上午| L(?:?(:\' S | ARD |子(?:\' s)))| R(?:???DEEN(:\' S)| nathy(?:??\' S)| RA (:???NT |和灰(:(:\' S | S))?))| T(:( ?:吨(:?????E(:R(:(:\& ????#39; S | S))| d)| ING |或(:(:\' S | S)))| S))| yance(:???\' S ?)| d))| HOR(:( ?: R(:E(为:n(:CE(:??????\' S)| T)| d)|荷兰国际集团)| S ?))| I(:????d(:E [DS] |荷兰国际集团|一月(:\' S))|盖尔| L(:???烯|它(:IES | Y(:\' S'))))| J〜(:ECT(:LY)| UR(:通货膨胀(:(:??????\' S | S))? | E [DS] |荷兰国际集团))| L(:一个(:略去(:(:????\' S | S))|???ZE)| E(:( ?: ST | ????R))| OOM | ution(:(:\' S | S))| Y)|米\' S | N(:????E(:GAT(:电子?[DS] | I(:?纳克|上(:\' S)?))| R(?:\' S))| ormal(:( ?:它(???? :IES | Y(?:\' S))| LY)))| O(:ARD |德(:(:??????\' S | S))|李( ?:SH |)|和灰(:(:\' S | IST(:(:( ?: E [DS]荷兰国际集团?)???(?:?\' S | S)) ?)))|米娜(:BL [EY] | T(:E [DS] | I(:纳克|上(:(:??????\' S | S))?)? ))| R(:????igin(:人(:(:\' S | S))| E'?(?:?(:\' S | S)))| ?吨(:( ?: ED | I(:纳克|上(:(:???\' S | IST(:(:???\' S | S))| S) ?)|阳离子)| S)))| U(:ND(:( ?:编| ING | S))| T)| VE(:(:??????\' S |板)))| R(:一个(:cadabra(:?????\' S)| d(:E [DS] |荷兰国际集团)|火腿(:???\' S) ?| M(?:?(:\' S | S))| SI(:????上(:(?:\' S | S))| VE(:(? :\' S | LY |岬(?:\' S)| S))))|东|腾讯科技讯(:?????E(:( ?:包换(:( ?: \' S | S))| [DS]))| ING |换货(:????(:\' S | S)))| O(:广告| GAT(???? ?:E [DS] | I(:???纳克|上(:(:\' S | S))?)))| UPT(:( ?: E(:?ST | r)的| LY |岬(?:\' S))))| S(:???ALOM | C(:ESS(:(:???\' S | E [DS] | ING ????))| ISSA(:(:\' S | [ES]))| OND(:( ?:编|在? ????克| S)))| EN(:CE(:(?:\' S | S))| T(:( ?: E(:E(:(:\???? ' S | ISM(?:\' S)| S))| d)| ING | LY | S)))| INTH(:?????(:\' S | E(?:\' S)))| O(:L(:UT(:E(:(:???????\' S | LY | ST))? | I(?:?上(:\' S)| SM(?:\' S)?))| v(?:E [DS] |荷兰国际集团))| R(α? :b(:( ?: E(为:n(:CY(:\' S)| T(:??????(?:?\' S | S)))| d )| ING | S))|?PTI ...
它真的难以理解,但是对于100000个被禁词的列表,这个Trie正则表达式比简单的正则表达式联合快1000倍!
这是一个完整的trie图,用trie-python-graphviz和graphviz twopi导出: