TLDR
Используйте этот метод (с заданным поиском), если вам нужно самое быстрое решение. Для набора данных, аналогичного OP, это примерно в 2000 раз быстрее, чем принятый ответ.
Если вы настаиваете на использовании регулярного выражения для поиска, используйте эту версию на основе дерева , которая по-прежнему в 1000 раз быстрее, чем объединение регулярных выражений.
теория
Если ваши предложения не являются огромными строками, вероятно, можно обработать намного больше, чем 50 в секунду.
Если вы сохраните все запрещенные слова в набор, будет очень быстро проверить, включено ли в этот набор другое слово.
Упакуйте логику в функцию, передайте эту функцию в качестве аргумента, re.sub
и все готово!
Код
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)
Преобразованные предложения:
' . !
.
GiraffeElephantBoat
sfgsdg sdwerha aswertwe
Обратите внимание, что:
- поиск без учета регистра (спасибо
lower()
)
- замена слова на
""
может оставить два пробела (как в вашем коде)
- С python3
\w+
также сопоставляет символы с диакритическими знаками (например, "ångström"
).
- Любой символ, не являющийся словом (табуляция, пробел, новая строка, знаки, ...), останется нетронутым.
Производительность
Есть миллион предложений, banned_words
почти 100000 слов, а сценарий выполняется менее чем за 7 секунд.
Для сравнения, ответ Liteye потребовал 160 секунд на 10 тысяч предложений.
Учитывая n
общее количество слов и m
количество запрещенных слов, код OP и Liteye равен O(n*m)
.
Для сравнения, мой код должен работать O(n+m)
. Учитывая, что предложений намного больше, чем запрещенных слов, алгоритм становится O(n)
.
Regex union test
В чем сложность поиска по регулярному выражению с использованием '\b(word1|word2|...|wordN)\b'
шаблона? Это O(N)
или O(1)
?
Довольно сложно понять, как работает движок регулярных выражений, поэтому давайте напишем простой тест.
Этот код извлекает 10**i
случайные английские слова в список. Он создает соответствующее объединение регулярных выражений и проверяет его с помощью разных слов:
- одно явно не слово (начинается с
#
)
- один - первое слово в списке
- один - последнее слово в списке
- один выглядит как слово, но не
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))
Он выводит:
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
Итак, похоже, что поиск одного слова с '\b(word1|word2|...|wordN)\b'
шаблоном имеет:
O(1)
лучший случай
O(n/2)
средний случай, который все еще O(n)
O(n)
худший случай
Эти результаты согласуются с простым циклическим поиском.
Гораздо более быстрая альтернатива объединению регулярных выражений - создание шаблона регулярного выражения из дерева .