ignorecase - python string expresiones regulares
Acelera millones de reemplazos de expresiones regulares en Python 3 (9)
TLDR
Use este método (con búsqueda establecida) si desea la solución más rápida. Para un conjunto de datos similar a los OP, es aproximadamente 2000 veces más rápido que la respuesta aceptada.
Si insiste en usar una expresión regular para la búsqueda, use esta versión basada en trie , que aún es 1000 veces más rápida que una unión de expresión regular.
Teoría
Si sus oraciones no son cadenas enormes, probablemente sea factible procesar muchas más de 50 por segundo.
Si guarda todas las palabras prohibidas en un conjunto, será muy rápido verificar si se incluye otra palabra en ese conjunto.
Empaque la lógica en una función,
re.sub
esta función como argumento para
re.sub
y listo.
Código
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)
Las oraciones convertidas son:
'' . !
.
GiraffeElephantBoat
sfgsdg sdwerha aswertwe
Tenga en cuenta que:
-
la búsqueda no distingue entre mayúsculas y minúsculas (gracias a
lower()
) -
reemplazar una palabra con
""
podría dejar dos espacios (como en su código) -
Con python3,
/w+
también coincide con los caracteres acentuados (por ejemplo,"ångström"
). - Cualquier carácter que no sea de palabra (tabulación, espacio, nueva línea, marcas, ...) permanecerá intacto.
Actuación
Hay un millón de oraciones,
banned_words
tiene casi 100000 palabras y el script se ejecuta en menos de 7 segundos.
En comparación, la answer Liteye necesitaba 160 para 10 mil oraciones.
Con
n
siendo la cantidad total de palabras
m
la cantidad de palabras prohibidas, los códigos de OP y Liteye son
O(n*m)
.
En comparación, mi código debería ejecutarse en
O(n+m)
.
Teniendo en cuenta que hay muchas más oraciones que palabras prohibidas, el algoritmo se convierte en
O(n)
.
Prueba de unión de expresiones regulares
¿Cuál es la complejidad de una búsqueda de
''/b(word1|word2|...|wordN)/b''
regulares con un
''/b(word1|word2|...|wordN)/b''
?
¿Es
O(N)
u
O(1)
?
Es bastante difícil comprender la forma en que funciona el motor regex, así que escribamos una prueba simple.
Este código extrae
10**i
palabras aleatorias en inglés en una lista.
Crea la unión regex correspondiente y la prueba con diferentes palabras:
-
uno claramente no es una palabra (comienza con
#
) - una es la primera palabra en la lista
- una es la última palabra en la lista
- uno parece una palabra pero no lo es
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))
Produce:
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
Por lo tanto, parece que la búsqueda de una sola palabra con un
''/b(word1|word2|...|wordN)/b''
tiene:
-
O(1)
mejor caso -
O(n/2)
caso promedio, que sigue siendoO(n)
-
O(n)
peor de los casos
Estos resultados son consistentes con una simple búsqueda de bucle.
Una alternativa mucho más rápida a una unión regex es crear el patrón regex a partir de un trie .
Estoy usando Python 3.5.2
Tengo dos listas
- una lista de aproximadamente 750,000 "oraciones" (cadenas largas)
- una lista de aproximadamente 20,000 "palabras" que me gustaría eliminar de mis 750,000 oraciones
Entonces, tengo que recorrer 750,000 oraciones y realizar alrededor de 20,000 reemplazos, pero SOLO si mis palabras son en realidad "palabras" y no son parte de una cadena de caracteres más grande.
Estoy haciendo esto al
precompilar
mis palabras para que estén flanqueadas por el metacarácter
/b
compiled_words = [re.compile(r''/b'' + word + r''/b'') for word in my20000words]
Luego recorro mis "oraciones"
import re
for sentence in sentences:
for word in compiled_words:
sentence = re.sub(word, "", sentence)
# put sentence into a growing list
Este bucle anidado procesa alrededor de 50 oraciones por segundo , lo cual es bueno, pero aún tarda varias horas en procesar todas mis oraciones.
-
¿Hay alguna manera de usar el método
str.replace
(que creo que es más rápido), pero que aún requiere que los reemplazos solo sucedan en los límites de las palabras ? -
Alternativamente, ¿hay alguna manera de acelerar el método
re.sub
? Ya he mejorado la velocidad marginalmente saltando sobrere.sub
si la longitud de mi palabra es> que la longitud de mi oración, pero no es una gran mejora.
Gracias por cualquier sugerencia
TLDR
Use este método si desea la solución más rápida basada en expresiones regulares. Para un conjunto de datos similar a los OP, es aproximadamente 1000 veces más rápido que la respuesta aceptada.
Si no le importa la expresión regular, use esta versión basada en conjuntos , que es 2000 veces más rápida que una unión de expresión regular.
Regex optimizado con Trie
Un enfoque answer se vuelve lento con muchas palabras prohibidas, porque el motor de expresiones regulares no hace un muy buen trabajo al optimizar el patrón.
Es posible crear un Trie con todas las palabras prohibidas y escribir la expresión regular correspondiente. El trie o regex resultante no son realmente legibles para los humanos, pero permiten una búsqueda y coincidencia muy rápidas.
Ejemplo
[''foobar'', ''foobah'', ''fooxar'', ''foozap'', ''fooza'']
La lista se convierte en un trie:
{
''f'': {
''o'': {
''o'': {
''x'': {
''a'': {
''r'': {
'''': 1
}
}
},
''b'': {
''a'': {
''r'': {
'''': 1
},
''h'': {
'''': 1
}
}
},
''z'': {
''a'': {
'''': 1,
''p'': {
'''': 1
}
}
}
}
}
}
}
Y luego a este patrón regex:
r"/bfoo(?:ba[hr]|xar|zap?)/b"
La gran ventaja es que para probar si el
zoo
coincide, el motor de expresiones regulares solo
necesita comparar el primer carácter
(no coincide), en lugar de
probar las 5 palabras
.
Es una exageración previa al proceso para 5 palabras, pero muestra resultados prometedores para muchos miles de palabras.
Tenga en cuenta que
(?:)
los grupos sin captura
se utilizan porque:
-
foobar|baz
coincidiría confoobar
obaz
, pero no confoobaz
-
foo(bar|baz)
guardaría información innecesaria en un grupo de captura .
Código
Aquí hay una
gist
ligeramente modificada, que podemos usar como una biblioteca
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())
Prueba
Aquí hay una pequeña prueba (la misma que esta ):
# 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))
Produce:
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
Para información, la expresión regular comienza así:
(?: a (?: (?: / ''s | a (?: /' s | chen | liyah (?: / ''s)? | r (?: dvark (?: (?: /' s | s ))? | on)) | b (?: / ''s | a (?: c (?: us (?: (?: /' s | es))? | [ik]) | ft | solitario (? : (?: / ''s | s))? | ndon (? :( ?: ed | ing | ment (?: /' s)? | s))? | s (?: e (? :( ?: ment (?: / ''s)? | [ds]))? | h (? :( ?: e [ds] | ing))? | ing) | t (?: e (? :( ?: ment ( ?: / ''s)? | [ds]))? | ing | toir (?: (?: /' s | s))?)) | b (?: as (?: id)? | e (? : ss (?: (?: / ''s | es))? | y (?: (?: /' s | s))?) | ot (?: (?: / ''s | t (?: / ''s)? | s))? | reviat (?: e [ds]? | i (?: ng | on (?: (?: /' s | s))?)) | y (?: / '' s)? | / é (?: (?: / ''s | s))?) | d (?: icat (?: e [ds]? | i (?: ng | on (?: (?: / ''s | s))?)) | om (?: en (?: (?: /' s | s))? | inal) | u (?: ct (? :( ?: ed | i (?: ng | on (?: (?: / ''s | s))?) | o (?: (?: /' s | s))? | s))? | l (?: / ''s)?) ) | e (?: (?: / ''s | am | l (?: (?: /' s | ard | son (?: / ''s)?))? | r (?: deen (?: / ''s)? | nathy (?: /' s)? | ra (?: nt | tion (?: (?: / ''s | s))?)) | t (? :( ?: t (?: e (?: r (?: (?: / ''s | s))? | d) | ing | o (?: (?: /' s | s))?) | s))? | yance (? : / ''s)? | d))? | hor (? :( ?: r (?: e (?: n (?: ce (?: /' s)? | t) | d) | ing) | s))? | i (?: d (?: e [ds]? | ing | jan (?: / ''s)?) | gail | l (?: ene | it (?: ies | y (?: / ''s)?))) | j (?: ect (?: ly)? | ur (?: ation (?: (?: /' s | s))? | e [ds]? | ing)) | l (?: a (?: tive (?: (?: / ''s | s))? | ze) | e (? :( ?: st | r))? | oom | ution (? :(? : / ''s | s))? y) | m / ''s | n (?: e (?: gat (?: e [ds]? | i (?: ng | on (?: /' s)?)) | r (?: / '' s)?) | ormal (? :( ?: it (?: ies | y (?: / ''s)?) | ly))?) | o (?: ard | de (?: (?: /' s | s))? | li (?: sh (? :( ?: e [ds] | ing))? | tion (?: (?: / ''s | ist (?: (?: /' s | s))?))?) | mina (?: bl [ey] | t (?: e [ds]? | i (?: ng | on (?: (?: / ''s | s))?) )) | r (?: igin (?: al (?: (?: / ''s | s))? | e (?: (?: /' s | s))?) | t (? :(? : ed | i (?: ng | on (?: (?: / ''s | ist (?: (?: /' s | s))? | s))? | ve) | s))?) | u (?: nd (? :( ?: ed | ing | s))? | t) | ve (?: (?: / ''s | tablero))?) | r (?: a (?: cadabra ( ?: / ''s)? | d (?: e [ds]? | ing) | ham (?: /' s)? | m (?: (?: / ''s | s))? | si (? : on (?: (?: / ''s | s))? | ve (?: (?: /' s | ly | ness (?: / ''s)? | s))?)) | east | idg (?: e (? :( ?: ment (?: (?: / ''s | s))? | [ds]))? | ing | ment (?: (?: /' s | s))? ) | o (?: ad | gat (?: e [ds]? | i (?: ng | on (?: (?: / ''s | s))?))) | upt (? :( ?: e (?: st | r) | ly | ness (?: / ''s)?))?) | s (?: alom | c (?: ess (?: (?: /' s | e [ds] | ing))? | issa (?: (?: / ''s | [es]))? | ond (? :( ?: ed | ing | s))?) | en (?: ce (? :( ?: / ''s | s))? | t (? :( ?: e (?: e (?: (?: /' s | ism (?: / ''s)? | s))? | d) | ing | ly | s))?) | inth (?: (?: / ''s | e (?: /' s)?))? | o (?: l (?: ut (?: e (? : (?: / ''s | ly | st?))? | i (?: on (?: /' s)? | sm (?: / ''s)?)) | v (?: e [ds] ? | ing)) | r (?: b (? :( ?: e (?: n (?: cy (?: / ''s)? | t (?: (?: /' s | s))? ) | d) | ing | s))? | pt yo...
Es realmente ilegible, pero para una lista de 100000 palabras prohibidas, esta expresión regular de Trie es 1000 veces más rápida que una simple unión de expresiones regulares.
Aquí hay un diagrama del trie completo, exportado con
trie-python-graphviz
y graphviz
twopi
:
Acercamiento práctico
Una solución que se describe a continuación utiliza mucha memoria para almacenar todo el texto en la misma cadena y reducir el nivel de complejidad. Si la RAM es un problema, piénselo dos veces antes de usarla.
Con los trucos de
join
/
split
, puede evitar los bucles que deberían acelerar el algoritmo.
merged_sentences = '' * ''.join(sentences)
|
"o" declaración regex:
regex = re.compile(r''/b({})/b''.format(''|''.join(words)), re.I) # re.I is a case insensitive flag
clean_sentences = re.sub(regex, "", merged_sentences).split('' * '')
Actuación
"".join
complejidad
"".join
es O (n).
Esto es bastante intuitivo, pero de todos modos hay una cita abreviada de una fuente:
for (i = 0; i < seqlen; i++) {
[...]
sz += PyUnicode_GET_LENGTH(item);
Por lo tanto, con
join/split
tienes O (palabras) + 2 * O (oraciones) que sigue siendo una complejidad lineal vs 2 * O (N
2
) con el enfoque inicial.
por cierto, no use multihilo. GIL bloqueará cada operación porque su tarea está estrictamente vinculada a la CPU, por lo que GIL no tiene posibilidad de ser liberada, pero cada subproceso enviará tics al mismo tiempo que causan un esfuerzo adicional e incluso llevan la operación al infinito.
Bueno, aquí hay una solución rápida y fácil, con conjunto de prueba.
Estrategia ganadora:
re.sub ("/ w +", repl, oración) busca palabras.
"repl" puede ser un invocable. Usé una función que realiza una búsqueda dict, y el dict contiene las palabras para buscar y reemplazar.
Esta es la solución más simple y rápida (vea la función replace4 en el código de ejemplo a continuación).
Segundo mejor
La idea es dividir las oraciones en palabras, usando re.split, mientras conserva los separadores para reconstruir las oraciones más tarde. Luego, los reemplazos se realizan con una simple búsqueda dict.
(vea la función replace3 en el código de ejemplo a continuación).
Tiempos por ejemplo funciones:
replace1: 0.62 sentences/s
replace2: 7.43 sentences/s
replace3: 48498.03 sentences/s
replace4: 61374.97 sentences/s (...and 240.000/s with PyPy)
... y código:
#! /bin/env python3
# -*- coding: utf-8
import time, random, re
def replace1( sentences ):
for n, sentence in enumerate( sentences ):
for search, repl in patterns:
sentence = re.sub( "//b"+search+"//b", repl, sentence )
def replace2( sentences ):
for n, sentence in enumerate( sentences ):
for search, repl in patterns_comp:
sentence = re.sub( search, repl, sentence )
def replace3( sentences ):
pd = patterns_dict.get
for n, sentence in enumerate( sentences ):
#~ print( n, sentence )
# Split the sentence on non-word characters.
# Note: () in split patterns ensure the non-word characters ARE kept
# and returned in the result list, so we don''t mangle the sentence.
# If ALL separators are spaces, use string.split instead or something.
# Example:
#~ >>> re.split(r"([^/w]+)", "ab céé? . d2eéf")
#~ [''ab'', '' '', ''céé'', ''? . '', ''d2eéf'']
words = re.split(r"([^/w]+)", sentence)
# and... done.
sentence = "".join( pd(w,w) for w in words )
#~ print( n, sentence )
def replace4( sentences ):
pd = patterns_dict.get
def repl(m):
w = m.group()
return pd(w,w)
for n, sentence in enumerate( sentences ):
sentence = re.sub(r"/w+", repl, sentence)
# Build test set
test_words = [ ("word%d" % _) for _ in range(50000) ]
test_sentences = [ " ".join( random.sample( test_words, 10 )) for _ in range(1000) ]
# Create search and replace patterns
patterns = [ (("word%d" % _), ("repl%d" % _)) for _ in range(20000) ]
patterns_dict = dict( patterns )
patterns_comp = [ (re.compile("//b"+search+"//b"), repl) for search, repl in patterns ]
def test( func, num ):
t = time.time()
func( test_sentences[:num] )
print( "%30s: %.02f sentences/s" % (func.__name__, num/(time.time()-t)))
print( "Sentences", len(test_sentences) )
print( "Words ", len(test_words) )
test( replace1, 1 )
test( replace2, 10 )
test( replace3, 1000 )
test( replace4, 1000 )
Concatena todas tus oraciones en un solo documento. Use cualquier implementación del algoritmo Aho-Corasick ( aquí hay uno ) para localizar todas sus palabras "malas". Recorre el archivo, reemplaza cada palabra incorrecta, actualiza los desplazamientos de las palabras encontradas que siguen, etc.
Qué tal esto:
#!/usr/bin/env python3
from __future__ import unicode_literals, print_function
import re
import time
import io
def replace_sentences_1(sentences, banned_words):
# faster on CPython, but does not use /b as the word separator
# so result is slightly different than replace_sentences_2()
def filter_sentence(sentence):
words = WORD_SPLITTER.split(sentence)
words_iter = iter(words)
for word in words_iter:
norm_word = word.lower()
if norm_word not in banned_words:
yield word
yield next(words_iter) # yield the word separator
WORD_SPLITTER = re.compile(r''(/W+)'')
banned_words = set(banned_words)
for sentence in sentences:
yield ''''.join(filter_sentence(sentence))
def replace_sentences_2(sentences, banned_words):
# slower on CPython, uses /b as separator
def filter_sentence(sentence):
boundaries = WORD_BOUNDARY.finditer(sentence)
current_boundary = 0
while True:
last_word_boundary, current_boundary = current_boundary, next(boundaries).start()
yield sentence[last_word_boundary:current_boundary] # yield the separators
last_word_boundary, current_boundary = current_boundary, next(boundaries).start()
word = sentence[last_word_boundary:current_boundary]
norm_word = word.lower()
if norm_word not in banned_words:
yield word
WORD_BOUNDARY = re.compile(r''/b'')
banned_words = set(banned_words)
for sentence in sentences:
yield ''''.join(filter_sentence(sentence))
corpus = io.open(''corpus2.txt'').read()
banned_words = [l.lower() for l in open(''banned_words.txt'').read().splitlines()]
sentences = corpus.split(''. '')
output = io.open(''output.txt'', ''wb'')
print(''number of sentences:'', len(sentences))
start = time.time()
for sentence in replace_sentences_1(sentences, banned_words):
output.write(sentence.encode(''utf-8''))
output.write(b'' .'')
print(''time:'', time.time() - start)
Estas soluciones se dividen en los límites de las palabras y busca cada palabra en un conjunto.
Deben ser más rápidos que las alternativas de segundo subtítulo (solución de Liteyes) ya que estas soluciones son
O(n)
donde n es el tamaño de la entrada debido a la búsqueda de conjunto
amortized O(1)
, mientras que el uso de alternativas de expresiones regulares causaría motor de expresiones regulares para tener que buscar coincidencias de palabras en cada carácter en lugar de solo en los límites de las palabras.
Mi solución es tener mucho cuidado para preservar los espacios en blanco que se utilizaron en el texto original (es decir, no comprime los espacios en blanco y conserva las pestañas, las nuevas líneas y otros caracteres de espacios en blanco), pero si decide que no le importa, debería ser bastante sencillo eliminarlos de la salida.
Probé en corpus.txt, que es una concatenación de múltiples libros electrónicos descargados del Proyecto Gutenberg, y banned_words.txt son 20000 palabras elegidas al azar de la lista de palabras de Ubuntu (/ usr / share / dict / american-english). Se necesitan alrededor de 30 segundos para procesar 862462 oraciones (y la mitad de eso en PyPy). He definido oraciones como cualquier cosa separada por ".".
$ # replace_sentences_1()
$ python3 filter_words.py
number of sentences: 862462
time: 24.46173644065857
$ pypy filter_words.py
number of sentences: 862462
time: 15.9370770454
$ # replace_sentences_2()
$ python3 filter_words.py
number of sentences: 862462
time: 40.2742919921875
$ pypy filter_words.py
number of sentences: 862462
time: 13.1190629005
PyPy se beneficia particularmente más del segundo enfoque, mientras que a CPython le fue mejor en el primer enfoque. El código anterior debería funcionar tanto en Python 2 como en 3.
Quizás Python no es la herramienta correcta aquí. Aquí hay uno con la cadena de herramientas Unix
sed G file |
tr '' '' ''/n'' |
grep -vf blacklist |
awk -v RS= -v OFS='' '' ''{$1=$1}1''
suponiendo que su archivo de lista negra esté preprocesado con los límites de palabras agregados. Los pasos son: convertir el archivo a doble espacio, dividir cada oración en una palabra por línea, eliminar en masa las palabras de la lista negra del archivo y fusionar las líneas.
Esto debería correr al menos un orden de magnitud más rápido.
Para preprocesar el archivo de la lista negra a partir de palabras (una palabra por línea)
sed ''s/.*///b&//b/'' words > blacklist
Una cosa que puede intentar es compilar un patrón único como
"/b(word1|word2|word3)/b"
.
Debido
re
depende del código C para hacer la coincidencia real, los ahorros pueden ser dramáticos.
Como @pvg señaló en los comentarios, también se beneficia de la coincidencia de un solo paso.
Si sus palabras no son expresiones regulares, la answer de Eric es más rápida.
Una cosa que quizás desee probar es preprocesar las oraciones para codificar los límites de las palabras. Básicamente, convierta cada oración en una lista de palabras dividiéndolas en los límites de las palabras.
Esto debería ser más rápido, porque para procesar una oración, solo tiene que pasar por cada una de las palabras y verificar si es una coincidencia.
Actualmente, la búsqueda de expresiones regulares tiene que pasar por la cadena completa de nuevo cada vez, buscar límites de palabras y luego "descartar" el resultado de este trabajo antes del próximo paso.