TLDR
ใช้วิธีนี้หากคุณต้องการโซลูชันที่ใช้ regex ที่เร็วที่สุด สำหรับชุดข้อมูลที่คล้ายกับ OP จะเร็วกว่าคำตอบที่ยอมรับประมาณ 1000 เท่า
หากคุณไม่สนใจเกี่ยวกับ regex ให้ใช้เวอร์ชันที่ใช้ชุดนี้ซึ่งเร็วกว่าสหภาพ regex ถึง 2,000 เท่า
ปรับปรุง Regex ด้วย Trie
ง่าย Regex สหภาพวิธีการจะช้าด้วยคำพูดต้องห้ามจำนวนมากเนื่องจากเครื่องยนต์ regex ไม่ไม่ได้ทำงานที่ดีมากของการเพิ่มประสิทธิภาพรูปแบบ
เป็นไปได้ที่จะสร้างTrieด้วยคำที่ต้องห้ามทั้งหมดและเขียน regex ที่เกี่ยวข้อง 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
}
}
}
}
}
}
}
จากนั้นไปยังรูปแบบ regex นี้:
r"\bfoo(?:ba[hr]|xar|zap?)\b"
ข้อได้เปรียบอย่างมากคือการที่จะทดสอบว่าzoo
ไม้ขีดไฟเครื่องยนต์ regex เพียงต้องการที่จะเปรียบเทียบตัวอักษรตัวแรก (มันไม่ตรง) แทนการพยายามที่ 5 คำ เป็นการใช้คำเกินขั้นตอนล่วงหน้าสำหรับ 5 คำ แต่แสดงผลลัพธ์ที่มีแนวโน้มสำหรับคำหลายพันคำ
โปรดทราบว่า(?:)
กลุ่มที่ไม่จับภาพถูกใช้เนื่องจาก:
foobar|baz
จะตรงfoobar
หรือbaz
, แต่ไม่foobaz
foo(bar|baz)
จะบันทึกข้อมูลที่ไม่จำเป็นไปยังกลุ่มจับ
รหัส
นี่คือส่วนสำคัญที่แก้ไขเล็กน้อยซึ่งเราสามารถใช้เป็นtrie.py
ไลบรารี:
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
สำหรับข้อมูล regex เริ่มต้นดังนี้:
(ที่: ((: \ 's | A (: \'?? s | chen | liyah (: \ 's) | R (:? dvark ((:? \' s | s )) | เมื่อ)) | B? (: \ 's | A (? C (: เรา ((: \?' s | e)) | [IK]) | ฟุต | คนเดียว (? (: \ 's | s)?) | ndon (? :( ?: เอ็ด | ไอเอ็นจี | ment (: \' s) |? s)) | s (: E (:( ?:? ment: | [ds])) | h (:( ?: อี [ds] | ไอเอ็นจี)) | ไอเอ็นจี) | T ((\ 's?):?? E (:( ?: ment ( ?: \ 's) | [ds])) | ไอเอ็นจี | toir (:? (: \?' s | s)))) | ข (: เป็น (??? id) | E (? : ss ((:? \ 's | e))? | Y ((: \?' |) | OT (s s)): (:? \ 's | ตัน (: \ 's) | s)) | reviat? (: อี [ds] | ฉัน (:? ng | เมื่อ (: (: \?' s | s)))) | Y (:? \' ? s) | \ é ((:? \ 's | s)?)) | d (: ICAT (อี [ds] | ฉัน (:? ng | เมื่อ (:? (: \ 's | s)))) | อ้อม (: TH ((: \?' s | s)) | Inal) | U (:? กะรัต (:( ?: เอ็ด | ฉัน (?: งะ | เมื่อ ((: \? 's | s))) | หรือ? ((:? \' s | s)????) | s)) | ลิตร (: \ 's)) ) | E ((:? \ 's | น | ลิตร ((:? \' s | อาด | ลูกชาย (:? \ 's))) | R (:? Deen (: \ 's) | nathy? (: \' s) | RA (:? NT | tion ((: \ 's | s))?)) | ตัน (:( ?: ตัน (?: E (: R ((: \? 's | s)) | d?) | ไอเอ็นจี | หรือ (:? (: \'s | s))) | S)) | Yance (:? \ 's) | d)) | hor (:( ?: R (??? E (: n (: CE (? : \ 's) | t) | d) | ไอเอ็นจี) | s)) | ฉัน (:? d (อี [ds] | ไอเอ็นจี | มกราคม (:? \' s?)) | gail | ลิตร (: ene | มัน (:? โอบอุ้ม | Y (: \ 's))?) | J | ur ((: ect (Ly?)?:? ation ((: \?)' s | s)) | E [ds] | ไอเอ็นจี)) | ลิตร (?:? (ที่: Tive ((:?? \ 's | s)) | ZE) | E (:(? : ST | R)) | ชอุ่ม | ution (:? (?:? \ 's? | s)) | y) | ม. \' s | n (: E (: GAT (อี [ds] ? | ฉัน (: ng | เมื่อ (: \ 's)?) | R (?: \?)' s)) | Ormal (:( ?: มัน (:? โอบอุ้ม | Y (:? \' s)) | Ly))) | o (:? อาด | เดอ ((:?? \ 's | s)) | li (: ดวลจุดโทษ (:( ?: อี [ds] | ไอเอ็นจี )) | tion? (:? (: \ 's | ist ((: \?' s | s))))) | มินา (:? BL [EY] | ตัน (?: E [ ds] | I? (: ng | เมื่อ (:? (: \ 's | s))))) | R (?:? igin (: อัล ((: \' s | s) ) | E? (:? (: \ 's | s))) | ตัน (:( ?: เอ็ด | ฉัน (:? ng | เมื่อ (: (:? \' s | ist (?: ) | s)) | ve) | s))) | U (| (\ 's s?):?? ND (:( ?: เอ็ด | ไอเอ็นจี | s)) | t) | ve ((:? \ 's | บอร์ด))) | R (:? (ที่: Cadabra (: \' s) | d (:? E [ds] | ไอเอ็นจี) | แฮม (? : \ 's) | เมตร ((:? \' s | s)?) | si (: บน ((:? \ 's | s)) |? ve (:( ?:?\ 's | Ly | Ness (: \' | s)))) | ตะวันออก | IDG (s):? E (:( ?: ment ((:? \ 's | s)) ? | [ds])) | ไอเอ็นจี | ment (:? (: \ 's | s))) | o (:? โฆษณา | GAT (อี [ds] | ฉัน (:? ng | เมื่อวันที่ ((: \? 's | s))))) | UPT (:( ?: อี (?:? ST | R) | Ly | Ness (: \' s)))) | s (:? Alom | C (: ESS ((: \ 's | E [ds] | ไอเอ็นจี)) | issa (:? (: \'? s | [e])) | ond)) | th (:( ?: เอ็ด | | ไอเอ็นจี s?)? (: CE ((:? \ 's | s)???) | ตัน (:( ?: อิเล็กทรอนิกส์ (อี ( : (: \ 's | ISM (: \' s) | s?)) | d) | ไอเอ็นจี | Ly | s))) | inth (:? (:? \ 's | E ( ?: \ 's))) | o? (:? ลิตร (: ยูทาห์ (: E ((: \?' s | Ly | ST)?) | ฉัน (:? ใน (?: \ 's) | เอสเอ็ม (: \' s?))) | v (อี [ds] | ไอเอ็นจี)) | R (:? B (:( ?: อี (: n (? : cy (: \ '| T s)? ((:? \' s | s))?) | d) | ไอเอ็นจี | s)) | PTI ...s | [e])) | ond (:( ?: เอ็ด | ไอเอ็นจี | s))) | th (?:? CE ((:? \ 's | S)) | ตัน (?: (อี (: E ((:?? \ 's? | ISM (: \'? s) | s)) | d) | ไอเอ็นจี | Ly | s))) | inth (?: (: \ 's | E (: \' s)?)) | o (:? ลิตร (: ยูทาห์ (: E ((: \ 's | Ly | ST))???? | ฉัน (: บน (: \ 's) | เอสเอ็ม (:? \'? s))?) | v (อี [ds] | ไอเอ็นจี)) | R (:? B (:( : E (: n (: cy (: \ 's) | ตัน (: (:? \'?? s | s))) | d)? | ไอเอ็นจี | s)) | PTI .. .s | [e])) | ond (:( ?: เอ็ด | ไอเอ็นจี | s))) | th (?:? CE ((:? \ 's | S)) | ตัน (?: (อี (: E ((:?? \ 's? | ISM (: \'? s) | s)) | d) | ไอเอ็นจี | Ly | s))) | inth (?: (: \ 's | E (: \' s)?)) | o (:? ลิตร (: ยูทาห์ (: E ((: \ 's | Ly | ST))???? | ฉัน (: บน (: \ 's) | เอสเอ็ม (:? \'? s))?) | v (อี [ds] | ไอเอ็นจี)) | R (:? B (:( : E (: n (: cy (: \ 's) | ตัน (: (:? \'?? s | s))) | d)? | ไอเอ็นจี | s)) | PTI .. .
มันอ่านไม่ออกจริงๆ แต่สำหรับรายการคำต้องห้าม 100,000 คำ Trie regex นี้เร็วกว่าสหภาพ regex ธรรมดาถึง 1,000 เท่า!
นี่คือแผนภาพของ trie ที่สมบูรณ์ซึ่งส่งออกด้วยtrie-python-graphvizและ graphviz twopi
: