TLDR
Use este método (com pesquisa de conjunto) se desejar a solução mais rápida. Para um conjunto de dados semelhante ao OP, é aproximadamente 2000 vezes mais rápido que a resposta aceita.
Se você insistir em usar um regex para pesquisa, use esta versão baseada em trie , que ainda é 1000 vezes mais rápida que uma união de regex.
Teoria
Se suas sentenças não forem seqüências de caracteres enormes, provavelmente é possível processar muito mais que 50 por segundo.
Se você salvar todas as palavras banidas em um conjunto, será muito rápido verificar se outra palavra está incluída nesse conjunto.
Empacote a lógica em uma função, dê essa função como argumento re.sub
e pronto!
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)
As frases convertidas são:
' . !
.
GiraffeElephantBoat
sfgsdg sdwerha aswertwe
Observe que:
- a pesquisa não diferencia maiúsculas de minúsculas (graças a
lower()
)
- substituir uma palavra por
""
pode deixar dois espaços (como no seu código)
- Com python3,
\w+
também combina caracteres acentuados (por exemplo "ångström"
).
- Qualquer caractere que não seja palavra (tab, espaço, nova linha, marcas, ...) permanecerá intocado.
atuação
Há um milhão de frases, banned_words
tem quase 100000 palavras e o script é executado em menos de 7s.
Em comparação, a resposta de Liteye precisava de 160s para 10 mil frases.
Com n
sendo o amound total de palavras e m
a quantidade de palavras proibidas, código de Liteye do OP e são O(n*m)
.
Em comparação, meu código deve ser executado O(n+m)
. Considerando que existem muito mais frases do que palavras proibidas, o algoritmo se torna O(n)
.
Teste de união Regex
Qual é a complexidade de uma pesquisa regex com um '\b(word1|word2|...|wordN)\b'
padrão? É O(N)
ou O(1)
?
É muito difícil entender o funcionamento do mecanismo regex, então vamos escrever um teste simples.
Esse código extrai 10**i
palavras aleatórias em inglês em uma lista. Ele cria a união de expressão regular correspondente e a testa com palavras diferentes:
- claramente não é uma palavra (começa com
#
)
- uma é a primeira palavra da lista
- uma é a última palavra da lista
- parece uma palavra, mas não é
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))
Emite:
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
Portanto, parece que a busca por uma única palavra com um '\b(word1|word2|...|wordN)\b'
padrão tem:
O(1)
melhor caso
O(n/2)
caso médio, que ainda é O(n)
O(n)
pior caso
Esses resultados são consistentes com uma pesquisa simples de loop.
Uma alternativa muito mais rápida a uma união de expressões regulares é criar o padrão de expressões regulares a partir de uma tentativa .