TLDR
Utilisez cette méthode (avec recherche d'ensemble) si vous voulez la solution la plus rapide. Pour un ensemble de données similaire aux PO, c'est environ 2000 fois plus rapide que la réponse acceptée.
Si vous insistez pour utiliser une expression régulière pour la recherche, utilisez cette version basée sur trie , qui est toujours 1000 fois plus rapide qu'une union d'expression régulière.
Théorie
Si vos phrases ne sont pas des chaînes énormes, il est probablement possible d'en traiter beaucoup plus de 50 par seconde.
Si vous enregistrez tous les mots interdits dans un ensemble, il sera très rapide de vérifier si un autre mot est inclus dans cet ensemble.
Emballez la logique dans une fonction, donnez cette fonction comme argument re.sub
et vous avez terminé!
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)
Les phrases converties sont:
' . !
.
GiraffeElephantBoat
sfgsdg sdwerha aswertwe
Notez que:
- la recherche est insensible à la casse (grâce à
lower()
)
- le remplacement d'un mot par
""
peut laisser deux espaces (comme dans votre code)
- Avec python3,
\w+
correspond également aux caractères accentués (par exemple "ångström"
).
- Tout caractère non-mot (tabulation, espace, nouvelle ligne, marques, ...) restera intact.
Performance
Il y a un million de phrases, banned_words
près de 100 000 mots et le script s'exécute en moins de 7 secondes.
En comparaison, la réponse de Liteye avait besoin de 160s pour 10 mille phrases.
Avec n
la quantité totale de mots et m
la quantité de mots interdits, les codes OP et Liteye le sont O(n*m)
.
En comparaison, mon code devrait fonctionner O(n+m)
. Considérant qu'il y a beaucoup plus de phrases que de mots interdits, l'algorithme devient O(n)
.
Test d'union regex
Quelle est la complexité d'une recherche regex avec un '\b(word1|word2|...|wordN)\b'
modèle? Est-ce O(N)
ou O(1)
?
Il est assez difficile de comprendre le fonctionnement du moteur regex, alors écrivons un test simple.
Ce code extrait 10**i
des mots anglais aléatoires dans une liste. Il crée l'union regex correspondante et la teste avec différents mots:
- on n'est clairement pas un mot (ça commence par
#
)
- l'un est le premier mot de la liste
- l'un est le dernier mot de la liste
- on ressemble à un mot mais ne l'est pas
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))
Il sort:
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
Il semble donc que la recherche d'un seul mot avec un '\b(word1|word2|...|wordN)\b'
motif ait:
O(1)
meilleur cas
O(n/2)
cas moyen, qui est toujours O(n)
O(n)
pire cas
Ces résultats sont cohérents avec une simple recherche en boucle.
Un beaucoup plus rapide alternative à un syndicat regex est de créer le modèle regex à partir d' une structure arborescente .