J'utilise Python 3.5.2
J'ai deux listes
- une liste d'environ 750 000 "phrases" (longues chaînes)
- une liste d'environ 20 000 "mots" que je voudrais supprimer de mes 750 000 phrases
Donc, je dois parcourir 750 000 phrases et effectuer environ 20 000 remplacements, mais UNIQUEMENT si mes mots sont en fait des «mots» et ne font pas partie d'une plus grande chaîne de caractères.
Je fais cela en pré-compilant mes mots afin qu'ils soient flanqués du \b
métacaractère
compiled_words = [re.compile(r'\b' + word + r'\b') for word in my20000words]
Puis je boucle mes «phrases»
import re
for sentence in sentences:
for word in compiled_words:
sentence = re.sub(word, "", sentence)
# put sentence into a growing list
Cette boucle imbriquée traite environ 50 phrases par seconde , ce qui est bien, mais cela prend encore plusieurs heures pour traiter toutes mes phrases.
Existe-t-il un moyen d'utiliser la
str.replace
méthode (qui, je pense, est plus rapide), tout en exigeant que les remplacements ne se produisent qu'aux limites des mots ?Sinon, existe-t-il un moyen d'accélérer la
re.sub
méthode? J'ai déjà légèrement amélioré la vitesse en sautantre.sub
si la longueur de mon mot est> à la longueur de ma phrase, mais ce n'est pas vraiment une amélioration.
Merci pour vos suggestions.
multiprocessing
(c'est-à-dire plusieurs processus Python).Réponses:
Une chose que vous pouvez essayer est de compiler un seul modèle comme
"\b(word1|word2|word3)\b"
.Étant donné
re
que la correspondance réelle repose sur le code C, les économies peuvent être considérables.Comme @pvg l'a souligné dans les commentaires, il bénéficie également de la correspondance en un seul passage.
Si vos mots ne sont pas des expressions régulières, la réponse d'Eric est plus rapide.
la source
s/They actually use/They actually could in theory sometimes use/
. Avez-vous des raisons de croire que l'implémentation de Python fait autre chose qu'une boucle ici?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
Les phrases converties sont:
Notez que:
lower()
)""
peut laisser deux espaces (comme dans votre code)\w+
correspond également aux caractères accentués (par exemple"ångström"
).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 etm
la quantité de mots interdits, les codes OP et Liteye le sontO(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 devientO(n)
.Test d'union regex
Quelle est la complexité d'une recherche regex avec un
'\b(word1|word2|...|wordN)\b'
modèle? Est-ceO(N)
ouO(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:#
)Il sort:
Il semble donc que la recherche d'un seul mot avec un
'\b(word1|word2|...|wordN)\b'
motif ait:O(1)
meilleur casO(n/2)
cas moyen, qui est toujoursO(n)
O(n)
pire casCes 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 .
la source
O(1)
affirmation trompeuse , votre réponse mérite certainement un vote favorable.TLDR
Utilisez cette méthode si vous voulez la solution basée sur les regex la plus rapide. Pour un ensemble de données similaire aux OP, c'est environ 1000 fois plus rapide que la réponse acceptée.
Si vous ne vous souciez pas des expressions régulières, utilisez cette version basée sur des ensembles , qui est 2000 fois plus rapide qu'une union regex.
Optimisé Regex avec Trie
Une approche simple d'union Regex devient lente avec de nombreux mots interdits, car le moteur de regex ne fait pas un très bon travail d'optimisation du modèle.
Il est possible de créer un Trie avec tous les mots interdits et d'écrire le regex correspondant. Le trie ou l'expression régulière qui en résulte ne sont pas vraiment lisibles par l'homme, mais ils permettent une recherche et une correspondance très rapides.
Exemple
La liste est convertie en un trie:
Et puis à ce modèle regex:
L'énorme avantage est que pour tester si les
zoo
correspondances, le moteur d'expression régulière n'a besoin que de comparer le premier caractère (il ne correspond pas), au lieu d' essayer les 5 mots . C'est un prétraitement excessif pour 5 mots, mais il montre des résultats prometteurs pour plusieurs milliers de mots.Notez que
(?:)
les groupes non capturants sont utilisés car:foobar|baz
correspondraitfoobar
oubaz
, mais pasfoobaz
foo(bar|baz)
enregistrerait les informations inutiles dans un groupe de capture .Code
Voici un résumé légèrement modifié , que nous pouvons utiliser comme
trie.py
bibliothèque:Tester
Voici un petit test (le même que celui-ci ):
Il sort:
Pour plus d'informations, l'expression régulière commence comme ceci:
C'est vraiment illisible, mais pour une liste de 100000 mots interdits, cette regex Trie est 1000 fois plus rapide qu'une simple union regex!
Voici un diagramme du trie complet, exporté avec trie-python-graphviz et graphviz
twopi
:la source
|
mais la capture de groupes n'est pas du tout nécessaire pour notre objectif. Ils ralentiraient simplement le processus et utiliseraient plus de mémoire sans avantage.\b
( limite du mot ). Si la liste est['apple', 'banana']
, elle remplacera les mots qui sont exactementapple
oubanana
, mais pasnana
,bana
oupineapple
.Une chose que vous voudrez peut-être essayer est de prétraiter les phrases pour encoder les limites des mots. Transformez chaque phrase en une liste de mots en la scindant sur les limites des mots.
Cela devrait être plus rapide, car pour traiter une phrase, il vous suffit de parcourir chacun des mots et de vérifier si c'est une correspondance.
Actuellement, la recherche regex doit parcourir à nouveau la chaîne entière à chaque fois, rechercher les limites des mots, puis "rejeter" le résultat de ce travail avant le passage suivant.
la source
Eh bien, voici une solution rapide et facile, avec ensemble de test.
Stratégie gagnante:
re.sub ("\ w +", repl, phrase) recherche des mots.
"repl" peut être un appelable. J'ai utilisé une fonction qui effectue une recherche de dict, et le dict contient les mots à rechercher et à remplacer.
C'est la solution la plus simple et la plus rapide (voir la fonction replace4 dans l'exemple de code ci-dessous).
Deuxième meilleur
L'idée est de diviser les phrases en mots, en utilisant re.split, tout en conservant les séparateurs pour reconstruire les phrases plus tard. Ensuite, les remplacements sont effectués avec une simple recherche de dict.
(voir la fonction replace3 dans l'exemple de code ci-dessous).
Timings pour les fonctions d'exemple:
... et code:
Modifier: vous pouvez également ignorer les minuscules lorsque vous vérifiez si vous passez une liste de phrases en minuscules et modifiez le repl
la source
replace4
et mon code a des performances similaires.repl(m):
et comment vous assignezm
dans la fonction replace4error: unbalanced parenthesis
patterns_comp = [ (re.compile("\\b"+search+"\\b"), repl) for search, repl in patterns ]
Peut-être que Python n'est pas le bon outil ici. En voici un avec la chaîne d'outils Unix
en supposant que votre fichier de liste noire est prétraité avec les limites de mots ajoutées. Les étapes sont les suivantes: convertir le fichier en double interligne, diviser chaque phrase en un mot par ligne, supprimer en masse les mots de la liste noire du fichier et fusionner les lignes.
Cela devrait fonctionner au moins un ordre de grandeur plus rapidement.
Pour prétraiter le fichier de liste noire à partir de mots (un mot par ligne)
la source
Que dis-tu de ça:
Ces solutions se divisent sur les limites des mots et recherchent chaque mot dans un ensemble. Ils devraient être plus rapides que re.sub of word alternates (solution de Liteyes) car ces solutions sont
O(n)
où n est la taille de l'entrée due à laamortized O(1)
recherche d'ensemble, tandis que l'utilisation de regex alternates obligerait le moteur regex à vérifier les correspondances de mots sur tous les caractères plutôt que sur les limites des mots. Ma solution prend un soin particulier à préserver les espaces qui ont été utilisés dans le texte d'origine (c'est-à-dire qu'il ne compresse pas les espaces et préserve les tabulations, les retours à la ligne et autres caractères d'espacement), mais si vous décidez que vous ne vous en souciez pas, il devrait être assez simple pour les supprimer de la sortie.J'ai testé sur corpus.txt, qui est une concaténation de plusieurs eBooks téléchargés à partir du projet Gutenberg, et banned_words.txt contient 20000 mots choisis au hasard dans la liste de mots d'Ubuntu (/ usr / share / dict / american-english). Il faut environ 30 secondes pour traiter 862462 phrases (dont la moitié sur PyPy). J'ai défini les phrases comme tout ce qui est séparé par ".".
PyPy bénéficie particulièrement davantage de la deuxième approche, tandis que CPython s'en sort mieux avec la première approche. Le code ci-dessus devrait fonctionner à la fois sur Python 2 et 3.
la source
\W+
c'est fondamentalement commesub
sur\w+
, non?Approche pratique
Une solution décrite ci-dessous utilise beaucoup de mémoire pour stocker tout le texte dans la même chaîne et pour réduire le niveau de complexité. Si la RAM est un problème, réfléchissez-y à deux fois avant de l'utiliser.
Avec
join
/split
tricks, vous pouvez éviter les boucles, ce qui devrait accélérer l'algorithme.|
instruction "ou" regex:Performance
"".join
la complexité est O (n). C'est assez intuitif mais de toute façon il y a une citation abrégée d'une source:Donc avec
join/split
vous avez O (mots) + 2 * O (phrases) qui est encore une complexité linéaire vs 2 * O (N 2 ) avec l'approche initiale.btw n'utilisez pas le multithreading. GIL bloquera chaque opération car votre tâche est strictement liée au processeur, donc GIL n'a aucune chance d'être libéré, mais chaque thread enverra des ticks simultanément, ce qui entraînera un effort supplémentaire et mènera même l'opération à l'infini.
la source
Concaténez toutes vos phrases en un seul document. Utilisez n'importe quelle implémentation de l'algorithme Aho-Corasick (en voici une ) pour localiser tous vos "mauvais" mots. Parcourez le fichier, remplacez chaque mauvais mot, mettez à jour les décalages des mots trouvés qui suivent, etc.
la source