Для одного из моих проектов мне понадобилось сделать фильтр мата. Сегодня мы попытаемся сделать его за несколько минут. Ну что же, приступим.
Анализирование
Сперва я должен бы как-то разбивать текст на части, чтобы потом сравнивать его с нецензурной лексикой. Решение нашлось очень просто. Я составил список запрещенных слов и стал проходится циклом по введенному тексту, разбивая его на куски размером с каждое запрещенное слово.
#Фраза, которую будем проверять.
phrase = input("Введите фразу для проверки: ")
#Искомое слово. Пока что только одно.
words = ["банан"]
#Фрагменты, которые получатся после разбиения слова.
fragments = []
#Проходимся по всем словам.
for word in words:
#Разбиваем слово на части, и проходимся по ним.
for part in range(len(phrase)):
#Вот сам наш фрагмент.
fragment = phrase[part: part+len(word)]
#Сохраняем его в наш список.
fragments.append(fragment)
#Выводим получившиеся фрагменты.
print(fragments)
Запускаем наш файл и вводим фразу.
Введите фразу для проверки: Привет, я банан.
['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я ба', 'я бан', ' бана', 'банан', 'анан.', 'нан.', 'ан.', 'н.', '.']
Вот что у нас получилось. Смотрим на все фрагменты и видим среди них искомое слово «банан». Теперь нам осталось сравнить эти фрагменты с искомыми словами.
Сравнение
Для того, чтобы сравнивать фрагменты с искомыми словами, я решил просто использовать цикл.
#Проходимся по всем словам.
for word in words:
#Проходимся по всем фрагментам.
for fragment in fragments:
#Сравниваем фрагмент и искомое слово
if word == fragment:
#Если они равны, выводим надпись о их нахождении.
print("Найдено", word)
Смотрим.
Введите фразу для проверки: Привет, я банан.
['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я ба', 'я бан', ' бана', 'банан', 'анан.', 'нан.', 'ан.', 'н.', '.']
Найдено банан
Все, простейший фильтр нецензурной лексики готов!
Доработки
Простейший фильтр был готов, но я решил его чуточку дополнить.
Так как русский человек очень изобретателен, то он может поменять некоторые буквы на другой язык. Например, «бaнaн». Здесь место обычной «а», я поставил английскую. И теперь наш фильтр не будет распознавать это слово.
Введите фразу для проверки: Привет, я бaнaн.
['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я бa', 'я бaн', ' бaнa', 'бaнaн', 'aнaн.', 'нaн.', 'aн.', 'н.', '.']
Фильтр не вывел ничего, а значит не нашел слово. Поэтому мы должны создать дополнительный фильтр, который будет переводить буквы английского алфавита и похожие символы в русский текст.
В интернете я нашел вот такой список, который чуточку доработал.
d = {'а' : ['а', 'a', '@'],
'б' : ['б', '6', 'b'],
'в' : ['в', 'b', 'v'],
'г' : ['г', 'r', 'g'],
'д' : ['д', 'd', 'g'],
'е' : ['е', 'e'],
'ё' : ['ё', 'e'],
'ж' : ['ж', 'zh', '*'],
'з' : ['з', '3', 'z'],
'и' : ['и', 'u', 'i'],
'й' : ['й', 'u', 'i'],
'к' : ['к', 'k', 'i{', '|{'],
'л' : ['л', 'l', 'ji'],
'м' : ['м', 'm'],
'н' : ['н', 'h', 'n'],
'о' : ['о', 'o', '0'],
'п' : ['п', 'n', 'p'],
'р' : ['р', 'r', 'p'],
'с' : ['с', 'c', 's'],
'т' : ['т', 'm', 't'],
'у' : ['у', 'y', 'u'],
'ф' : ['ф', 'f'],
'х' : ['х', 'x', 'h' , '}{'],
'ц' : ['ц', 'c', 'u,'],
'ч' : ['ч', 'ch'],
'ш' : ['ш', 'sh'],
'щ' : ['щ', 'sch'],
'ь' : ['ь', 'b'],
'ы' : ['ы', 'bi'],
'ъ' : ['ъ'],
'э' : ['э', 'e'],
'ю' : ['ю', 'io'],
'я' : ['я', 'ya']
}
Перед фильтрацией мы должны перевести весь текст в нижний регистр и убрать все пробелы, так как кто-нибудь может ввести искомые слова вот так: «БАНАН» или «б а н а н».
phrase = phrase.lower().replace(" ", "")
Теперь мы должны как-то сравнить этот список с нашим текстом. Для этого я создал вот такую функцию.
#Проходимся по нашему словарю.
for key, value in d.items():
#Проходимся по каждой букве в значении словаря. То есть по вот этим спискам ['а', 'a', '@'].
for letter in value:
#Проходимся по каждой букве в нашей фразе.
for phr in phrase:
#Если буква совпадает с буквой в нашем списке.
if letter == phr:
#Заменяем эту букву на ключ словаря.
phrase = phrase.replace(phr, key)
Что у нас получилось теперь.
Введите фразу для проверки: Привет, я б@н@н.
['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я ба', 'я бан', ' бана', 'банан', 'анан.', 'нан.', 'ан.', 'н.', '.']
Найдено банан
То, что у нас не находилось раньше теперь легко распознаётся.
Расстояние Левенштейна
Я понял, что если хоть чуточку изменить слово, то его уже невозможно найти.
Введите фразу для проверки: Я люблю бонан.
['Я люб', ' любл', 'люблю', 'юблю ', 'блю б', 'лю бо', 'ю бон', ' бона', 'бонан', 'онан.', 'нан.', 'ан.', 'н.', '.']
Наши опасения подтвердились, но решения этой проблемы есть расстояние Левенштейна.
В интернете я нашел функцию этого алгоритма для python. Вот как она выглядит.
def distance(a, b):
"Calculates the Levenshtein distance between a and b."
n, m = len(a), len(b)
if n > m:
# Make sure n <= m, to use O(min(n, m)) space
a, b = b, a
n, m = m, n
current_row = range(n + 1) # Keep current and previous row, not entire matrix
for i in range(1, m + 1):
previous_row, current_row = current_row, [i] + [0] * n
for j in range(1, n + 1):
add, delete, change = previous_row[j] + 1, current_row[j - 1] + 1, previous_row[j - 1]
if a[j - 1] != b[i - 1]:
change += 1
current_row[j] = min(add, delete, change)
return current_row[n]
Теперь мы должны переписать функцию сравнения.
#Проходимся по всем словам.
for word in words:
#Проходимся по всем фрагментам.
for fragment in fragments:
#Если отличие этого фрагмента меньше или равно 25% этого слова, то считаем, что они равны.
if distance(fragment, word) <= len(word)*0.25:
#Если они равны, выводим надпись о их нахождении.
print("Найдено", word)
Что у нас получилось теперь.
Введите фразу для проверки: Я люблю бонан.
['Я люб', ' любл', 'люблю', 'юблю ', 'блю б', 'лю бо', 'ю бон', ' бона', 'бонан', 'онан.', 'нан.', 'ан.', 'н.', '.']
Найдено банан
Небольшие проблемы
Наверное вы уже догадались, что если в списке для фильтрации будет больше одного слова, то он будет некорректно работать. Поэтому я переписал это всё в один цикл.
#Проходимся по всем словам.
for word in words:
#Разбиваем слово на части, и проходимся по ним.
for part in range(len(phrase)):
#Вот сам наш фрагмент.
fragment = phrase[part: part+len(word)]
#Если отличие этого фрагмента меньше или равно 25% этого слова, то считаем, что они равны.
if distance(fragment, word) <= len(word)*0.25:
#Если они равны, выводим надпись о их нахождении.
print("Найдено", word, "\nПохоже на", fragment)
Все, наш фильтр полностью готов.
Код фильтра
import string
words = ["банан", "помидор"]
print("Фильтруемые слова:", words)
#Фраза, которую будем проверять.
phrase = input("Введите фразу для проверки: ").lower().replace(" ", "")
def distance(a, b):
"Calculates the Levenshtein distance between a and b."
n, m = len(a), len(b)
if n > m:
# Make sure n <= m, to use O(min(n, m)) space
a, b = b, a
n, m = m, n
current_row = range(n + 1) # Keep current and previous row, not entire matrix
for i in range(1, m + 1):
previous_row, current_row = current_row, [i] + [0] * n
for j in range(1, n + 1):
add, delete, change = previous_row[j] + 1, current_row[j - 1] + 1, previous_row[j - 1]
if a[j - 1] != b[i - 1]:
change += 1
current_row[j] = min(add, delete, change)
return current_row[n]
d = {'а' : ['а', 'a', '@'],
'б' : ['б', '6', 'b'],
'в' : ['в', 'b', 'v'],
'г' : ['г', 'r', 'g'],
'д' : ['д', 'd'],
'е' : ['е', 'e'],
'ё' : ['ё', 'e'],
'ж' : ['ж', 'zh', '*'],
'з' : ['з', '3', 'z'],
'и' : ['и', 'u', 'i'],
'й' : ['й', 'u', 'i'],
'к' : ['к', 'k', 'i{', '|{'],
'л' : ['л', 'l', 'ji'],
'м' : ['м', 'm'],
'н' : ['н', 'h', 'n'],
'о' : ['о', 'o', '0'],
'п' : ['п', 'n', 'p'],
'р' : ['р', 'r', 'p'],
'с' : ['с', 'c', 's'],
'т' : ['т', 'm', 't'],
'у' : ['у', 'y', 'u'],
'ф' : ['ф', 'f'],
'х' : ['х', 'x', 'h' , '}{'],
'ц' : ['ц', 'c', 'u,'],
'ч' : ['ч', 'ch'],
'ш' : ['ш', 'sh'],
'щ' : ['щ', 'sch'],
'ь' : ['ь', 'b'],
'ы' : ['ы', 'bi'],
'ъ' : ['ъ'],
'э' : ['э', 'e'],
'ю' : ['ю', 'io'],
'я' : ['я', 'ya']
}
for key, value in d.items():
#Проходимся по каждой букве в значении словаря. То есть по вот этим спискам ['а', 'a', '@'].
for letter in value:
#Проходимся по каждой букве в нашей фразе.
for phr in phrase:
#Если буква совпадает с буквой в нашем списке.
if letter == phr:
#Заменяем эту букву на ключ словаря.
phrase = phrase.replace(phr, key)
#Проходимся по всем словам.
for word in words:
#Разбиваем слово на части, и проходимся по ним.
for part in range(len(phrase)):
#Вот сам наш фрагмент.
fragment = phrase[part: part+len(word)]
#Если отличие этого фрагмента меньше или равно 25% этого слова, то считаем, что они равны.
if distance(fragment, word) <= len(word)*0.25:
#Если они равны, выводим надпись о их нахождении.
print("Найдено", word, "\nПохоже на", fragment)
Комментарии (14)
sedyh
30.11.2023 20:12В первом случае у вас получился нечеткий поиск по пятиграммам. Низкая эффективность была в основном из-за размера сканирующего окна. В случае триграмм результат был бы лучше. Кроме того, во всех случаях у вас будет большое количество ложноположительных срабатываний, потребуется довольно большой словарь исключений.
Ps: В ссылке на расстояние Левенштейна пропущено двоеточие.
MountainGoat
30.11.2023 20:12+2Словарь исключений - всегда провальная концепция.
-- С уважением, индус Мухападхуяй.NickDoom
30.11.2023 20:12Но, простите, чего такого непристойного в мухопаде? Они всегда это делают по осени…
Grey83
30.11.2023 20:12+1А ещё некоторые пишут с ошибками используя обсценную лексику. Потому что даже матерные слова написать без ошибок не умеют.
В этом случае Ваш фильтр мало поможет.
Кроме того есть любители использовать весь юникод (привет иероглифы и прочие символы разных языков, похожие очертаниями на буквы кириллицы/латиницы).Πㄗ|/|ß∑ㅜ、я ɓⓐㅐαH
AlexTheCleaner
Такие пятиминутные фильтры обычно слово "оскорблять" банят.))
csharpreader
Слово «аналитика» ещё очень страдает )
IvanPetrof
Потому что нельзя людей оскорблять))