Архив метки: pymorphy2

Генерация множественных числительных в согласовании с существительным на python

Суть задачи: написать функцию, на входе у которой число и существительное, на выходе сгенерированное числительное и существительное в правильном склонении.
Такая задача очень часто возникает например в финансовой сфере, так как на документах принято писать сумму помимо цифрой — ещё и прописью.

Алгоритм склонения множественно числа очень прост. Всего существует только три варианта для любого числа. Возьмём слово доллар, тогда
Вариант 1: именительный падеж, например один доллар, тридцать один доллар, много-много-один доллар. т.е. когда число оканчивается на 1
Вариант 2: Родительный падеж, например два доллара, тридцать три доллара, много-много-четыре доллара. т.е. когда число оканчивается на 2, 3 и 4
Вариант 3: Множественная форма, Родительный падеж. например пять долларово, много-много-восемь долларов. т.е. когда число оканчивается на 5, 6, 7, 8, 9, 0
Случай с нулём лучше рассмотреть отдельно в силу различных требований от того что вы действительно хотите получить.

Внимание! Для чисел оканчивающихся на 11, 12, 13, 14 есть особенность. Поэтому
основной алгоритм выбора формы существительного после числительного будет таким:
Шаг 1: Если число оканчивается на 1, но не оканчивается на 11, то вариант 1 (Именительный падеж)
Шаг 2: Если число оканчивается на 2, 3, 4, и не оканчивается на 12, 13, 14, то вариант 2 (Родительный падеж)
Шаг 3: Всё остальное — вариант 3 (Множественный родительный падеж)

Теперь вернемся к нашей изначальной задаче, ведь на входе у нас есть только одно существительное. Значит надо уметь его поставить в одну из этих форм. Воспользуемся библиотекой на python — pymorphy2 :

import pymorphy2

morph = pymorphy2.MorphAnalyzer()
word = morph.parse('доллар')[0]
v1, v2, v3 = word.inflect({'sing', 'nomn'}), word.inflect({'gent'}), word.inflect({'plur', 'gent'})

print(v1.word, v2.word, v3.word)  # доллар доллара долларов

Если кто знаком с библиотекой pymorphy2, то возможно знают, что можно напрямую воспользоваться методом make_agree_with_number . Это действительно так, и не надо воротить лишний код, но есть одно Но — библиотека не умеет генерировать сами числительные, а лишь согласовывать, ставя в нужную форму
существительное. Чтобы именно генерировать — воспользуемся библиотекой, найденной мной на github — https://github.com/seriyps/ru_number_to_text/blob/master/num2t4ru/__init__.py (ru_number_to_text-master). Отмечу, что библиотека делает даже чуть больше чем генерация целых числительных, также возможна генерация дробных числительных. И так, создадим финальную функцию, с импортом указанной библиотеки

from num2t4ru import num2text
import pymorphy2

def get_number_and_noun(numeral, noun):
    morph = pymorphy2.MorphAnalyzer()
    word = morph.parse(noun)[0]
    v1, v2, v3 = word.inflect({'sing', 'nomn'}), word.inflect({'gent'}), word.inflect({'plur', 'gent'})
    return num2text(num=numeral, main_units=((v1.word, v2.word, v3.word), 'm'))

result = get_number_and_noun(123452, 'доллар')  
print(result)  # сто двадцать три тысячи четыреста пятьдесят два доллара

В качестве практического применения сгенерированных числительных — представьте что вам надо создать много документов word, в каждый из которых требуется подставить полученные числа. Причем сгенерированные числа с согласуемым существительным можно в дальнейшем просклонять с помощью pymorphy2. (в текущем примере числительные в именительном падеже)

Как определить одушевленность существительного на Python?

Предположим, что нам на вход подается произвольное слово и необходимо узнать является ли оно одушевленным или нет. Причем одушевленность надо проверить только для имен существительных. Один из простых способов узнать одушевленность это:

  1. Проверить, является ли слово существительным
  2. поставить слово в форму винительного падежа множественного числа
  3. поставить слово в форму родительного падежа множественного числа
  4. сравнить 2 и 3 и если формы совпадут, то слово будет одушевленным, в противном случае — неодушевленным

Согласно данному алгоритмы напишем код на python с использованием pymorphy2:

# -*- coding: utf-8 -*-
import pymorphy2

def isAnimateNoun(word):
    morph = pymorphy2.MorphAnalyzer()
    parsed = morph.parse( word )

    #if not parsed, return False
    if len(parsed) == 0:
        return False

    #let's check if word is Noun.
    #if we have multiple variations, let's take first
    noun_word = None
    for variant in parsed:
        if {'NOUN'} in variant.tag:
            noun_word = variant
            break

    if noun_word is None:
        return False

    #checking forms
    #множественное число родительного и винительного падежа
    gent_case = noun_word.inflect({'plur', 'gent'})
    accs_case = noun_word.inflect({'plur', 'accs'})
    if gent_case is None or accs_case is None:
        return False

    if gent_case.word == accs_case.word:
        print(gent_case.word)
        return True
    else:
        return False

print(isAnimateNoun(u'динозавр'))

Данный способ весьма прост, но его точность гарантировать не буду. Т.к. в случае мужского рода частенько проверяют формы слова 2 и 3 и для единственного числа. Однако, разработчики pymorphy2 уже озадачились данной проблемой и оказывается достаточно написать следующий код:

# -*- coding: utf-8 -*-
import pymorphy2

def isAnimateNoun2(word):
    morph = pymorphy2.MorphAnalyzer()
    parsed = morph.parse( word )

    #if not parsed, return False
    if len(parsed) == 0:
        return False

    #let's check if word is Noun.
    #if we have multiple variations, let's take first
    for variant in parsed:
        if {'NOUN'} in variant.tag:
            if variant.tag.animacy is not None and variant.tag.animacy == "anim": #inan, anim
                return True

    return False

print(isAnimateNoun2(u'динозавр'))

Вот и все. Отмечу лишь, что в коде мы учитываем возможную морфологическую неоднозначность, т.е. если слово может принимать формы разных частей речи , то мы берем только имена существительные.

Национальности мира в мужском роде на pymorphy2

Получить файл с национальностями мира не так то уж и сложно, достаточно зайти на ту же википедию, скопировать искомую таблицу и немного подредактировать. Что собственно я и сделал, получился следующий файл ( скачать национальности мира txt ). Однако все источники, которые удалось найти, предлагают национальности только во множественном числе, т.е. не «китаец» а «китайцы», не «японец», а «японцы».  А как Вы могли понять, для моей задачи требуется выделять национальность в мужском роде единственного числа. Немного поразмыслив, решил воспользоваться морфологическим анализатором pymorphy2, который написан на Python и позволяет ставить слово в нужную форму. Пишем следующий скрипт:

# -*- coding: utf-8 -*-
import pymorphy2

morph = pymorphy2.MorphAnalyzer()

generated_nationalities = [] #

file = open('all_nationality.txt', 'r')
for line in file.readlines():
    line = line.strip() #deleting unnecessary spaces
    if ' ' in line: continue #not processing several words
    parsed = morph.parse( line.decode('utf-8') )
    if len(parsed) == 0: continue #if pymorphy converted
    parsed = parsed[0]
    res = parsed.inflect({'sing', 'nomn'})
    if res is None: continue #continue if can not generate
    print(res.word)

    generated_nationalities.append( line + ', ' + res.word.encode('utf-8') )
file.close()

#writing to file
file_generated = open('generated_nationalities.txt', 'w')
file_generated.write( "\n".join(generated_nationalities) )
file_generated.close()

Немного поясню код:

  1. Сначала подключаем библиотеку pymorphy2 и инициализируем морфологический анализатор.
  2. Для каждой строки, содержащую национальность, убираем ненужные пробелы и знак переноса строки
  3. Если национальность состоит из нескольких слов, то пропускаем (возможно в следующий раз будем изменять словосочетания вроде «суринамцы индийского происхождения» но для текущей задачи это избыточно )
  4. Генерируем национальность в единственном числе и добавляем в список
  5. На последнем шаге происходит запись в файл полученного списка

В результате исполнения данной программы получился следующий файл - национальности в мужском роде txt Хорошо бы было ещё получить национальности и в женском роде, но pymorphy этого делать пока не позволяет