Skip to content

Commit

Permalink
Adding Serbian language (#31)
Browse files Browse the repository at this point in the history
* Reorg code, add Serbian group 2 and some utils for lexicon parsing.

* Added exceptions and 4th group is done.

* Added initial group 3, no special cases, and training data generator.

* Group 1 rough rules, didn't cover complications in vocative.

* Finish sr group 3. Refactor utils for priority_union. Add more tests and exceptions for sr.
  • Loading branch information
nciric authored May 21, 2024
1 parent 80e16e0 commit 32dee64
Show file tree
Hide file tree
Showing 7 changed files with 769 additions and 0 deletions.
112 changes: 112 additions & 0 deletions data/sr/exceptions.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
кћи:nom:sg кћи
кћи:gen:sg кћери
кћи:dat:sg кћери
кћи:acc:sg кћер
кћи:voc:sg кћери
кћи:ins:sg кћери
кћи:loc:sg кћери
кћи:nom:pl кћери
кћи:gen:pl кћери
кћи:dat:pl кћерима
кћи:acc:pl кћери
кћи:voc:pl кћери
кћи:ins:pl кћерима
кћи:loc:pl кћерима
мати:nom:sg мати
мати:gen:sg матере
мати:dat:sg матери
мати:acc:sg матер
мати:voc:sg мати
мати:ins:sg матером
мати:loc:sg матери
мати:nom:pl матере
мати:gen:pl матера
мати:dat:pl матерама
мати:acc:pl матере
мати:voc:pl матере
мати:ins:pl матерама
мати:loc:pl матерама
мама:nom:sg мама
мама:gen:sg маме
мама:dat:sg мами
мама:acc:sg маму
мама:voc:sg мама
мама:ins:sg мамом
мама:loc:sg мами
мама:nom:pl маме
мама:gen:pl мама
мама:dat:pl мамама
мама:acc:pl маме
мама:voc:pl маме
мама:ins:pl мамама
мама:loc:pl мамама
Ана:nom:sg Ана
Ана:gen:sg Ане
Ана:dat:sg Ани
Ана:acc:sg Ану
Ана:voc:sg Ана
Ана:ins:sg Аном
Ана:loc:sg Ани
Ана:nom:pl Ане
Ана:gen:pl Ана
Ана:dat:pl Анама
Ана:acc:pl Ане
Ана:voc:pl Ане
Ана:ins:pl Анама
Ана:loc:pl Анама
Италија:nom:sg Италија
Италија:gen:sg Италије
Италија:dat:sg Италији
Италија:acc:sg Италију
Италија:voc:sg Италијо
Италија:ins:sg Италијом
Италија:loc:sg Италији
Италија:nom:pl Италије
Италија:gen:pl Италија
Италија:dat:pl Италијама
Италија:acc:pl Италије
Италија:voc:pl Италије
Италија:ins:pl Италијама
Италија:loc:pl Италијама
рука:nom:sg рука
рука:gen:sg руке
рука:dat:sg руки
рука:acc:sg руку
рука:voc:sg руко
рука:ins:sg руком
рука:loc:sg руки
рука:nom:pl руке
рука:gen:pl руку
рука:dat:pl рукама
рука:acc:pl руке
рука:voc:pl руке
рука:ins:pl рукама
рука:loc:pl рукама
слуга:nom:sg слуга
слуга:gen:sg слуге
слуга:dat:sg слуги
слуга:acc:sg слугу
слуга:voc:sg слуго
слуга:ins:sg слугом
слуга:loc:sg слуги
слуга:nom:pl слуге
слуга:gen:pl слугу
слуга:dat:pl слугама
слуга:acc:pl слуге
слуга:voc:pl слуге
слуга:ins:pl слугама
слуга:loc:pl слугама
нога:nom:sg нога
нога:gen:sg ноге
нога:dat:sg ноги
нога:acc:sg ногу
нога:voc:sg ного
нога:ins:sg ногом
нога:loc:sg ноги
нога:nom:pl ноге
нога:gen:pl ногу
нога:dat:pl ногама
нога:acc:pl ноге
нога:voc:pl ноге
нога:ins:pl ногама
нога:loc:pl ногама
57 changes: 57 additions & 0 deletions fst/inflection_en.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
Inflects English nouns - creates a plural form from singular.
Run from project root folder.
Install Pynini package before running.
"""

import os
import pynini as p

from pynini.lib import pynutil
from pynini.lib import rewrite
from utils import priority_union

_v = p.union('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')
_c = p.union('b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n',
'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z',
'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N',
'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z')
_sigma = p.union(_v, _c).closure().optimize()

# Load exceptions from the file.
_exceptions = p.string_file(os.path.normpath('data/en/exceptions.tsv'))

# If a singular noun ends in -y and the letter before the -y is a consonant, change the ending to -ies.
# city - cities
# puppy - puppies
_ies = _sigma + _c + p.cross('y', 'ies')

# If the singular noun ends in -s, -ss, -sh, -ch, -x or -z, you usually add -es to the end.
# truss - trusses
# marsh - marshes
# lunch - lunches
# tax - taxes
# blitz - blitzes

# Exceptions:
# iris - irises
# bus - busses
# fez - fezzes
_es = _sigma + p.union('ss', 'sh', 'ch', 'x', 'z') + pynutil.insert('es')

# To make regular nouns plural, add -s to the end.
# cat - cats
# house - houses
_s = _sigma + pynutil.insert("s")

# Exclusions of rules are done through priority unions.
# Order is important, as _s rule should come in the end (catch all).
# _exceptions->(_ies, _a, _i)->_es->s
_plural = priority_union(
_exceptions, priority_union(_ies, priority_union(_es, _s, _sigma),
_sigma), _sigma).optimize()

def inflect(singular: str) -> str:
return rewrite.one_top_rewrite(singular, _plural)
59 changes: 59 additions & 0 deletions fst/inflection_en_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from inflection_en import inflect
import unittest


class TestClassification(unittest.TestCase):

def test_exceptions(self):
self.assertEqual('busses', inflect('bus'))
self.assertEqual('fezzes', inflect('fez'))
self.assertEqual('irises', inflect('iris'))
self.assertEqual('wives', inflect('wife'))
self.assertEqual('wolves', inflect('wolf'))
self.assertEqual('photos', inflect('photo'))
self.assertEqual('pianos', inflect('piano'))
self.assertEqual('halos', inflect('halo'))
self.assertEqual('feet', inflect('foot'))
self.assertEqual('pence', inflect('penny'))
self.assertEqual('sheep', inflect('sheep'))
self.assertEqual('series', inflect('series'))
self.assertEqual('species', inflect('species'))
self.assertEqual('deer', inflect('deer'))
self.assertEqual('children', inflect('child'))
self.assertEqual('geese', inflect('goose'))
self.assertEqual('men', inflect('man'))
self.assertEqual('women', inflect('woman'))
self.assertEqual('teeth', inflect('tooth'))
self.assertEqual('mice', inflect('mouse'))
self.assertEqual('people', inflect('person'))
self.assertEqual('cacti', inflect('cactus'))
self.assertEqual('foci', inflect('focus'))
self.assertEqual('phenomena', inflect('phenomenon'))
self.assertEqual('criteria', inflect('criterion'))
self.assertEqual('potatoes', inflect('potato'))
self.assertEqual('Mays', inflect('May'))
self.assertEqual('Julys', inflect('July'))

def test_s(self):
self.assertEqual('cats', inflect('cat'))
self.assertEqual('houses', inflect('house'))
self.assertEqual('roofs', inflect('roof'))
self.assertEqual('Januaries', inflect('January'))
self.assertEqual('Marches', inflect('March'))
self.assertEqual('Junes', inflect('June'))
self.assertEqual('Decembers', inflect('December'))
self.assertEqual('Mondays', inflect('Monday'))

def test_ies(self):
self.assertEqual('cities', inflect('city'))
self.assertEqual('puppies', inflect('puppy'))

def test_es(self):
self.assertEqual('trusses', inflect('truss'))
self.assertEqual('marshes', inflect('marsh'))
self.assertEqual('lunches', inflect('lunch'))
self.assertEqual('taxes', inflect('tax'))
self.assertEqual('blitzes', inflect('blitz'))

if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 32dee64

Please sign in to comment.