TLDR
Verwenden Sie diese Methode (mit Set-Lookup), wenn Sie die schnellste Lösung wünschen. Bei einem Datensatz, der den OPs ähnlich ist, ist er ungefähr 2000-mal schneller als die akzeptierte Antwort.
Wenn Sie darauf bestehen, einen regulären Ausdruck für die Suche zu verwenden, verwenden Sie diese trie-basierte Version , die immer noch 1000-mal schneller ist als eine reguläre Vereinigung.
Theorie
Wenn Ihre Sätze keine riesigen Zeichenfolgen sind, ist es wahrscheinlich möglich, viel mehr als 50 pro Sekunde zu verarbeiten.
Wenn Sie alle gesperrten Wörter in einem Satz speichern, können Sie sehr schnell überprüfen, ob ein anderes Wort in diesem Satz enthalten ist.
Packen Sie die Logik in eine Funktion, geben Sie diese Funktion als Argument an re.sub
und Sie sind fertig!
Code
import re
with open('/usr/share/dict/american-english') as wordbook:
banned_words = set(word.strip().lower() for word in wordbook)
def delete_banned_words(matchobj):
word = matchobj.group(0)
if word.lower() in banned_words:
return ""
else:
return word
sentences = ["I'm eric. Welcome here!", "Another boring sentence.",
"GiraffeElephantBoat", "sfgsdg sdwerha aswertwe"] * 250000
word_pattern = re.compile('\w+')
for sentence in sentences:
sentence = word_pattern.sub(delete_banned_words, sentence)
Konvertierte Sätze sind:
' . !
.
GiraffeElephantBoat
sfgsdg sdwerha aswertwe
Beachten Sie, dass:
- Bei der Suche wird die Groß- und Kleinschreibung nicht berücksichtigt (danke an
lower()
).
- Wenn Sie ein Wort durch ersetzen, bleiben
""
möglicherweise zwei Leerzeichen (wie in Ihrem Code).
- Stimmt mit python3
\w+
auch mit Akzentzeichen überein (z "ångström"
. B. ).
- Alle Nicht-Wort-Zeichen (Tabulator, Leerzeichen, Zeilenumbruch, Markierungen, ...) bleiben unberührt.
Performance
Es gibt eine Million Sätze, banned_words
hat fast 100000 Wörter und das Skript läuft in weniger als 7 Sekunden.
Im Vergleich dazu benötigte Liteyes Antwort 160 Sekunden für zehntausend Sätze.
Mit n
der Gesamt amound von Wörtern zu sein und m
die Menge der verbotenen Wörter, OPs und Liteye Code sind O(n*m)
.
Im Vergleich dazu sollte mein Code in laufen O(n+m)
. Wenn man bedenkt, dass es viel mehr Sätze als verbotene Wörter gibt, wird der Algorithmus O(n)
.
Regex Union Test
Wie komplex ist eine Regex-Suche mit einem '\b(word1|word2|...|wordN)\b'
Muster? Ist es O(N)
oder O(1)
?
Es ist ziemlich schwer zu verstehen, wie die Regex-Engine funktioniert. Schreiben wir also einen einfachen Test.
Dieser Code extrahiert 10**i
zufällige englische Wörter in eine Liste. Es erstellt die entsprechende Regex-Union und testet sie mit verschiedenen Worten:
- man ist eindeutig kein Wort (es beginnt mit
#
)
- Eines ist das erste Wort in der Liste
- Eins ist das letzte Wort in der Liste
- man sieht aus wie ein Wort, ist es aber nicht
import re
import timeit
import random
with open('/usr/share/dict/american-english') as wordbook:
english_words = [word.strip().lower() for word in wordbook]
random.shuffle(english_words)
print("First 10 words :")
print(english_words[:10])
test_words = [
("Surely not a word", "#surely_NöTäWORD_so_regex_engine_can_return_fast"),
("First word", english_words[0]),
("Last word", english_words[-1]),
("Almost a word", "couldbeaword")
]
def find(word):
def fun():
return union.match(word)
return fun
for exp in range(1, 6):
print("\nUnion of %d words" % 10**exp)
union = re.compile(r"\b(%s)\b" % '|'.join(english_words[:10**exp]))
for description, test_word in test_words:
time = timeit.timeit(find(test_word), number=1000) * 1000
print(" %-17s : %.1fms" % (description, time))
Es gibt aus:
First 10 words :
["geritol's", "sunstroke's", 'fib', 'fergus', 'charms', 'canning', 'supervisor', 'fallaciously', "heritage's", 'pastime']
Union of 10 words
Surely not a word : 0.7ms
First word : 0.8ms
Last word : 0.7ms
Almost a word : 0.7ms
Union of 100 words
Surely not a word : 0.7ms
First word : 1.1ms
Last word : 1.2ms
Almost a word : 1.2ms
Union of 1000 words
Surely not a word : 0.7ms
First word : 0.8ms
Last word : 9.6ms
Almost a word : 10.1ms
Union of 10000 words
Surely not a word : 1.4ms
First word : 1.8ms
Last word : 96.3ms
Almost a word : 116.6ms
Union of 100000 words
Surely not a word : 0.7ms
First word : 0.8ms
Last word : 1227.1ms
Almost a word : 1404.1ms
Es sieht also so aus, als hätte die Suche nach einem einzelnen Wort mit einem '\b(word1|word2|...|wordN)\b'
Muster Folgendes:
O(1)
I'm besten fall
O(n/2)
Durchschnittsfall, der noch ist O(n)
O(n)
schlimmsten Fall
Diese Ergebnisse stimmen mit einer einfachen Schleifensuche überein.
Eine viel schnellere Alternative zu einer Regex-Vereinigung besteht darin, das Regex-Muster aus einem Versuch zu erstellen .