Я использую Python 3.5.2
У меня есть два списка
- список из примерно 750 000 «предложений» (длинных строк)
- список примерно из 20 000 «слов», которые я хотел бы удалить из своих 750 000 предложений
Итак, мне нужно перебрать 750 000 предложений и выполнить около 20 000 замен, но ТОЛЬКО если мои слова на самом деле являются «словами» и не являются частью более крупной строки символов.
Я делаю это, предварительно компилируя свои слова так, чтобы они были окружены \b
метасимволом
compiled_words = [re.compile(r'\b' + word + r'\b') for word in my20000words]
Затем я прокручиваю свои "предложения"
import re
for sentence in sentences:
for word in compiled_words:
sentence = re.sub(word, "", sentence)
# put sentence into a growing list
Этот вложенный цикл обрабатывает около 50 предложений в секунду , что неплохо, но для обработки всех моих предложений все равно требуется несколько часов.
Есть ли способ использовать этот
str.replace
метод (который, как я считаю, быстрее), но при этом требовать, чтобы замены выполнялись только на границах слов ?Как вариант, есть ли способ ускорить
re.sub
метод? Я уже немного увеличил скорость, пропустив,re.sub
если длина моего слова>, чем длина моего предложения, но это не очень хорошее улучшение.
Спасибо за любые предложения.
multiprocessing
(т.е. несколько процессов Python).Ответы:
Вы можете попробовать скомпилировать один-единственный шаблон вроде
"\b(word1|word2|word3)\b"
.Поскольку
re
фактическое сопоставление выполняется с помощью кода C, экономия может быть значительной.Как отметил @pvg в комментариях, он также выигрывает от однопроходного сопоставления.
Если ваши слова не являются регулярным выражением, Эрик ответит быстрее.
источник
s/They actually use/They actually could in theory sometimes use/
. Есть ли у вас основания полагать, что реализация Python здесь делает что-то, кроме цикла?TLDR
Используйте этот метод (с заданным поиском), если вам нужно самое быстрое решение. Для набора данных, аналогичного OP, это примерно в 2000 раз быстрее, чем принятый ответ.
Если вы настаиваете на использовании регулярного выражения для поиска, используйте эту версию на основе дерева , которая по-прежнему в 1000 раз быстрее, чем объединение регулярных выражений.
теория
Если ваши предложения не являются огромными строками, вероятно, можно обработать намного больше, чем 50 в секунду.
Если вы сохраните все запрещенные слова в набор, будет очень быстро проверить, включено ли в этот набор другое слово.
Упакуйте логику в функцию, передайте эту функцию в качестве аргумента,
re.sub
и все готово!Код
Преобразованные предложения:
Обратите внимание, что:
lower()
)""
может оставить два пробела (как в вашем коде)\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
случайные английские слова в список. Он создает соответствующее объединение регулярных выражений и проверяет его с помощью разных слов:#
)Он выводит:
Итак, похоже, что поиск одного слова с
'\b(word1|word2|...|wordN)\b'
шаблоном имеет:O(1)
лучший случайO(n/2)
средний случай, который все ещеO(n)
O(n)
худший случайЭти результаты согласуются с простым циклическим поиском.
Гораздо более быстрая альтернатива объединению регулярных выражений - создание шаблона регулярного выражения из дерева .
источник
O(1)
утверждение, ваш ответ определенно заслуживает голосования.TLDR
Используйте этот метод, если вам нужно самое быстрое решение на основе регулярных выражений. Для набора данных, аналогичного OP, это примерно в 1000 раз быстрее, чем принятый ответ.
Если вас не волнует регулярное выражение, используйте эту версию на основе набора , которая в 2000 раз быстрее, чем объединение регулярных выражений.
Оптимизированное регулярное выражение с Trie
Простой Regex союз подход становится медленным со многими запрещенными словами, потому что движок регулярных выражений не делает очень хорошую работу по оптимизации шаблона.
Можно создать Trie со всеми запрещенными словами и написать соответствующее регулярное выражение. Получающееся в результате дерево или регулярное выражение на самом деле не читается человеком, но они позволяют очень быстрый поиск и сопоставление.
пример
Список преобразуется в дерево:
А затем к этому шаблону регулярного выражения:
Огромное преимущество заключается в том, что для проверки
zoo
совпадений механизму регулярных выражений нужно сравнить только первый символ (он не совпадает), вместо того, чтобы пробовать 5 слов . Это излишний препроцесс для 5 слов, но он показывает многообещающие результаты для многих тысяч слов.Обратите внимание, что группы
(?:)
без захвата используются, потому что:foobar|baz
будет соответствоватьfoobar
илиbaz
, но неfoobaz
foo(bar|baz)
сохранит ненужную информацию в группе захвата .Код
Вот немного измененная суть , которую мы можем использовать как
trie.py
библиотеку:Тест
Вот небольшой тест (такой же, как этот ):
Он выводит:
Для информации, регулярное выражение начинается так:
Это действительно нечитабельно, но для списка из 100000 запрещенных слов это регулярное выражение Trie в 1000 раз быстрее, чем простое объединение регулярных выражений!
Вот диаграмма полного дерева, экспортированного с помощью trie-python-graphviz и graphviz
twopi
:источник
|
но группы захвата вообще не нужны для нашей цели. Они просто замедлили бы процесс и без пользы использовали бы больше памяти.\b
( границе слова ). Если список есть['apple', 'banana']
, он заменит слова, которые в точности соответствуютapple
илиbanana
, но неnana
,bana
илиpineapple
.Одна вещь, которую вы можете попробовать, - это предварительная обработка предложений для кодирования границ слов. Обычно превращайте каждое предложение в список слов, разделяя их по границам слов.
Это должно быть быстрее, потому что для обработки предложения вам просто нужно пройти по каждому слову и проверить, соответствует ли оно.
В настоящее время поиск по регулярному выражению должен каждый раз проходить всю строку снова, ища границы слов, а затем «отбрасывая» результат этой работы перед следующим проходом.
источник
Что ж, вот быстрое и простое решение с тестовым набором.
Стратегия победы:
re.sub ("\ w +", repl, предложение) ищет слова.
"repl" может быть вызываемым. Я использовал функцию, которая выполняет поиск по словарю, и он содержит слова для поиска и замены.
Это самое простое и быстрое решение (см. Функцию replace4 в примере кода ниже).
Второе место
Идея состоит в том, чтобы разбить предложения на слова с помощью re.split, сохранив при этом разделители для последующего восстановления предложений. Затем замены выполняются с помощью простого поиска по словарю.
(см. функцию replace3 в примере кода ниже).
Сроки для примера функций:
... и код:
Изменить: вы также можете игнорировать строчные буквы при проверке, передаете ли вы список предложений в нижнем регистре и редактируете ответ
источник
replace4
и мой код имеют похожие характеристики.repl(m):
делает def и как вы назначаетеm
в функции replace4error: unbalanced parenthesis
в строкеpatterns_comp = [ (re.compile("\\b"+search+"\\b"), repl) for search, repl in patterns ]
Возможно, Python здесь не тот инструмент. Вот один с набором инструментов Unix
предполагая, что ваш файл черного списка предварительно обработан с добавленными границами слов. Эти шаги: преобразовать файл в двойной интервал, разбить каждое предложение на одно слово в строке, массово удалить слова из черного списка из файла и снова объединить строки.
Это должно работать как минимум на порядок быстрее.
Для предварительной обработки файла черного списка из слов (по одному слову в строке)
источник
Как насчет этого:
Эти решения разбиваются по границам слов и ищут каждое слово в наборе. Они должны быть быстрее, чем re.sub of word alternates (решение Liteyes), поскольку в этих решениях
O(n)
n - размер ввода из-заamortized O(1)
установленного поиска, в то время как использование альтернатив регулярных выражений приведет к тому, что механизм регулярных выражений должен будет проверять совпадения слов на всех символах, а не только на границах слов. Мое решениеa проявляет особую осторожность, чтобы сохранить пробелы, которые использовались в исходном тексте (т.е. он не сжимает пробелы и не сохраняет табуляции, новые строки и другие символы пробелов), но если вы решите, что вам это не важно, это должно быть довольно просто удалить их из вывода.Я тестировал corpus.txt, который представляет собой объединение нескольких электронных книг, загруженных из проекта Gutenberg, а banned_words.txt - это 20000 слов, случайно выбранных из списка слов Ubuntu (/ usr / share / dict / american-english). На обработку 862462 предложений (и половину на PyPy) уходит около 30 секунд. Я определил предложения как все, что разделено ".".
PyPy особенно выигрывает от второго подхода, в то время как CPython лучше справляется с первым подходом. Приведенный выше код должен работать как на Python 2, так и на Python 3.
источник
\W+
основном , какsub
на\w+
, не так ли?Практический подход
В описанном ниже решении используется много памяти для хранения всего текста в одной строке и для снижения уровня сложности. Если проблема с оперативной памятью, подумайте дважды, прежде чем использовать ее.
С помощью
join
/split
tricks можно вообще избежать циклов, что должно ускорить алгоритм.|
выражение регулярного выражения "или":Производительность
"".join
сложность O (n). Это довольно интуитивно понятно, но в любом случае есть сокращенная цитата из источника:Следовательно, у
join/split
вас есть O (слова) + 2 * O (предложения), что по-прежнему является линейной сложностью по сравнению с 2 * O (N 2 ) с начальным подходом.кстати, не используйте многопоточность. GIL будет блокировать каждую операцию, потому что ваша задача строго привязана к процессору, поэтому GIL не может быть освобожден, но каждый поток будет отправлять тики одновременно, что вызывает дополнительные усилия и даже приводит операцию к бесконечности.
источник
Объедините все свои предложения в один документ. Используйте любую реализацию алгоритма Ахо-Корасика ( вот такую ), чтобы найти все ваши «плохие» слова. Просмотрите файл, заменяя каждое плохое слово, обновляя смещения найденных слов, которые следуют за ним и т. Д.
источник