Как стать автором
Обновить

Как я чистила текст от ударений в словах при помощи Python

Уровень сложностиПростой
Время на прочтение5 мин
Количество просмотров4.6K

Трагедия гуманитария в трех актах.

Важный дисклеймер: все, написанное в статье ниже - это исключительно нубский взгляд на решение проблемы. Но конечным результатом я вполне довольна, поэтому и выношу его на публичный суд.

Так сложились обстоятельства, что передо мной встала задача создать базу данных словаря-тезауруса для дальнейшего использования в лингвистических исследованиях. Собственно, лингвистика - это и есть моя специальность, к IT я имею весьма опосредованное отношение в виде одного (незаконченного) онлайн-курса по основам Python и одного семестра программирования в универе (на который я почти не ходила).

На руках у меня была электронная версия сверстанного для печати словаря в формате .pdf, текст которого после извлечения выглядел примерно вот так:

Небольшой фрагмент для сравнения

Ажиота́ж, перен.
Ажиота́жный
Ажита́ция, устар.
Аза́рт
Аффе́кт
Безу́мство, перен.
Беспа́мятство
Беспоко́йно
Беспоко́йный
Беспоко́йство
Беспоко́иться
Беспоря́дки

Вообще, сам текст в целом получился очень "грязным", приведенный пример далеко не отражает всех тех проблем, с которыми я столкнулась. Но то, как я его чистила - это совсем другая история.

Пока что сфокусируемся на одной проблеме - ударениях. Ударения в словах полезны для человека, который к словарю подходит с позиции пользователя. Но исследователю они только усложняют жизнь, потому что делают практически невозможной задачу автоматического поиска по словам. Поэтому я приняла волевое решение сразу от них избавиться.

Итак, какие способы я опробовала:

  1. Замена гласных букв с диакритикой на обычные при помощи метода replace()

  2. То же самое, но с использованием метода translate()

  3. Удаление диакритики с использованием модуля unicodedata

Метод replace()

Это, наверное, один из первых (если не самый первый) метод, который изучают при знакомстве со строками в Python. Так что само собой разумеется, что первым делом я подумала про него.

Опробуем его на каком-нибудь одном слове:

s = "Беспа́мятство"
new = s.replace('а́', 'а')
print(new)

В результате получаем:

Беспамятство

Работает! Вот только появляется одна очевидная проблема. Гласных букв в русском алфавите всего 10: а, я, у, ю, о, е, ё, э, и, ы. Букву ё можно пока выкинуть, потому что над ней ударение не ставится, и тогда остается 9.

Но знак ударения у нас может быть и над первой буквой в слове, так что к строчным вариантам букв добавляются прописные - теперь их всего 17 (букву ы можно не учитывать, потому что в этом словаре нет слов, которые бы с нее начинались). Итого - 17 вариантов замен и 34 буквы в целом (набор с диакритикой и без). Конечно, можно было бы весь текст привести к строчному написанию, но я этого делать пока не хочу в силу других причин.

В общем, решение какое-то громоздкое получается. И на практике я от него сразу отказалась. Но вот сейчас решила развлечения ради посмотреть, как это будет выглядеть:

#Сначала заведем списки букв с ударением и без
no_accent = ['а', 'я', 'у', 'ю', 'о', 'е', 'э', 'и', 'ы', 'А', 'Я', 'У', 'Ю', 'О', 'Е', 'Э', 'И']
accented = ['а́', 'я́', 'у́', 'ю́', 'о́', 'е́', 'э́', 'и́', 'ы́', 'А́', 'Я́', 'У́', 'Ю́', 'О́', 'Е́', 'Э́', 'И́']

#Вручную прописывать каждую замену - долго и муторно
#Поэтому определим новую функцию
def replace_characters(original, new, str):
    for i in range(len(original) - 1):
        str = str.replace(original[i], new[i])
    return str

#Возьмем в качестве примера строку подлиннее  
str = "Беспа́мятство\nБеспоко́йно\nБеспоко́йный\nБеспоко́йство\nБеспоко́иться\nБеспоря́дки\nА́ховый"
res = replace_characters(accented, no_accent, str)
print(res)

Результат:

Беспамятство
Беспокойно
Беспокойный
Беспокойство
Беспокоиться
Беспорядки
Аховый

В принципе, все работает, на этом можно было бы остановиться. Но как-то не понравилась мне идея вручную задавать списки вариантов для замены.

Метод translate()

На самом деле в эту сторону я во время решения задачи даже не смотрела по той же самой причине, по которой я не стала в итоге использовать метод replace(). Но давайте проверим, как оно будет работать. В конце концов, список букв я уже задала выше, так что код напишется очень быстро.

no_accent = ['а', 'я', 'у', 'ю', 'о', 'е', 'э', 'и', 'ы', 'А', 'Я', 'У', 'Ю', 'О', 'Е', 'Э', 'И']
accented = ['а́', 'я́', 'у́', 'ю́', 'о́', 'е́', 'э́', 'и́', 'ы́', 'А́', 'Я́', 'У́', 'Ю́', 'О́', 'Е́', 'Э́', 'И́']

#Зададим таблицу перевода при помощи функции zip()
trans_table = dict(zip(accented, no_accent))

str = "Беспа́мятство\nБеспоко́йно\nБеспоко́йный\nБеспоко́йство\nБеспоко́иться\nБеспоря́дки\nА́ховый"

res = str.translate(trans_table)
print(res)

Выглядит лаконичнее, но результат...

Беспа́мятство
Беспоко́йно
Беспоко́йный
Беспоко́йство
Беспоко́иться
Беспоря́дки
А́ховый

... нулевой. Почему оно не сработало - я на правах нуба не имею ни малейшего понятия. Возможно, кто-нибудь мне объяснит.

Модуль unicodedata

А вот это решение я в итоге и использовала в своем финальном варианте кода. Разочаровавшись в методе replace(), я пошла бороздить просторы интернета в поисках более изящного кода и наткнулась на подходящее обсуждение на Stack Overflow.

Правда, речь там шла про удаление диакритики из текста на английском языке, что, как оказалось, не на 100% работает с русским. Но об этом позже.

В общем, взгляд мой зацепил вот этот кусочек кода, предложенный пользователем MiniQuark:

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])

И я решила опробовать его на своем материале:

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])

str = "Беспа́мятство\nБеспоко́йно\nБеспоко́йный\nБеспоко́йство\nБеспоко́иться\nБеспоря́дки\nА́ховый"
res = remove_accents(str)
print(res)

Результат:

Беспамятство
Беспокоино
Беспокоиныи
Беспокоиство
Беспокоиться
Беспорядки
Аховыи

Кажется, что-то пошло немного не так...

Проблема в том, что эта функция полностью убирает диакритические знаки. А в русском языке есть две буквы с диакритикой - это многострадальная Ё и Й. И если буквой Ё еще можно пожертвовать (хотя конкретно здесь - нельзя, потому что в словаре она используется для различения произносительных вариантов слов), то букву Й терять никак нельзя.

Кто-нибудь другой, наверное, закопался бы глубже и подкорректировал функцию таким образом, чтобы она убирала только знак ударения, а другую диакритику не трогала. Но я все-таки нубка, и решения у меня нубские.

Поэтому я задала функцию для временной замены нужных мне букв специальными знаками, которые больше нигде не используются в тексте и не создадут путаницы (да, это та же функция, которую я использовала выше).

def replace_characters(original, new, str):
    for i in range(len(original) - 1):
        str = str.replace(original[i], new[i])
    return str

#буквы, которые нужно сохранить
char_preserve = ["й", "ё", "Ё"]
#знаки, на которые мы будем их менять
placeholders = ["@", "#", "%"]

str = "Беспа́мятство\nБеспоко́йно\nБеспоко́йный\nБеспоко́йство\nБеспоко́иться\nБеспоря́дки\nА́ховый"
temp = replace_characters(char_preserve, placeholders, str)
print(temp)

В результате получаем такую красоту:

Беспа́мятство
Беспоко́@но
Беспоко́@ны@
Беспоко́@ство
Беспоко́иться
Беспоря́дки
А́ховы@

Теперь осталось только убрать диакритику при помощи уже заданной функции remove_accents и произвести обратную замену символов на буквы:

temp = remove_accents(temp)
res = replace_characters(placeholders, char_preserve, temp)
print(res)

Конечный результат:

Беспамятство
Беспокойно
Беспокойный
Беспокойство
Беспокоиться
Беспорядки
Аховый

В общем, как уже говорилось выше, в конечном итоге я воспользовалась последним способом. Все-таки здесь мне пришлось вручную прописывать 3 замены, что в разы меньше, чем 17 в случае с методом replace(). Ну а метод translate(), по всей видимости, останется примером того, как делать не надо.

Теги:
Хабы:
Всего голосов 11: ↑5 и ↓6+1
Комментарии19

Публикации

Истории

Работа

Python разработчик
201 вакансия
Data Scientist
98 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн