TLDR
Utilizzare questo metodo (con la ricerca impostata) se si desidera la soluzione più veloce. Per un set di dati simile ai PO, è circa 2000 volte più veloce della risposta accettata.
Se si insiste sull'uso di un regex per la ricerca, utilizzare questa versione basata su trie , che è ancora 1000 volte più veloce di un'unione regex.
Teoria
Se le tue frasi non sono stringhe gigantesche, è probabilmente possibile elaborare molte più di 50 al secondo.
Se salvi tutte le parole vietate in un set, sarà molto veloce controllare se un'altra parola è inclusa in quel set.
Comprimere la logica in una funzione, assegnare questa funzione come argomento re.sub
e il gioco è fatto!
Codice
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)
Le frasi convertite sono:
' . !
.
GiraffeElephantBoat
sfgsdg sdwerha aswertwe
Nota che:
- la ricerca non fa distinzione tra maiuscole e minuscole (grazie a
lower()
)
- la sostituzione di una parola con
""
potrebbe lasciare due spazi (come nel tuo codice)
- Con python3,
\w+
corrisponde anche a caratteri accentati (ad es "ångström"
.).
- Qualsiasi carattere non verbale (tab, spazio, nuova riga, segni, ...) rimarrà intatto.
Prestazione
Ci sono un milione di frasi, banned_words
ha quasi 100000 parole e lo script viene eseguito in meno di 7 secondi.
In confronto, la risposta di Liteye aveva bisogno di 160 secondi per 10 mila frasi.
Con n
l'ammontare totale di parole e m
la quantità di parole vietate, sono OP e il codice di Liteye O(n*m)
.
In confronto, il mio codice dovrebbe essere eseguito O(n+m)
. Considerando che ci sono molte più frasi delle parole vietate, l'algoritmo diventa O(n)
.
Test di unione Regex
Qual è la complessità di una ricerca regex con uno '\b(word1|word2|...|wordN)\b'
schema? È O(N)
o O(1)
?
È piuttosto difficile capire come funziona il motore regex, quindi scriviamo un semplice test.
Questo codice estrae 10**i
parole inglesi casuali in un elenco. Crea l'unione regex corrispondente e la verifica con parole diverse:
- uno chiaramente non è una parola (inizia con
#
)
- una è la prima parola nell'elenco
- una è l'ultima parola nell'elenco
- uno sembra una parola ma non lo è
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))
Emette:
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
Quindi sembra che la ricerca di una sola parola con un '\b(word1|word2|...|wordN)\b'
modello abbia:
O(1)
caso migliore
O(n/2)
caso medio, che è ancora O(n)
O(n)
caso peggiore
Questi risultati sono coerenti con una semplice ricerca in loop.
Un'alternativa molto più rapida a un'unione regex è quella di creare il modello regex da un trie .