001package squidpony;
002
003import regexodus.*;
004import squidpony.squidmath.CrossHash;
005import squidpony.squidmath.GapShuffler;
006import squidpony.squidmath.RNG;
007import squidpony.squidmath.StatefulRNG;
008
009import java.io.Serializable;
010import java.util.ArrayList;
011import java.util.Collection;
012import java.util.LinkedHashMap;
013import java.util.Map;
014
015import static squidpony.Maker.*;
016
017/**
018 * A text processing class that can swap out occurrences of words and replace them with their synonyms.
019 * Created by Tommy Ettinger on 5/23/2016.
020 */
021public class Thesaurus implements Serializable{
022    private static final long serialVersionUID = 3387639905758074640L;
023    protected static final Pattern wordMatch = Pattern.compile("([\\pL`]+)");
024    public LinkedHashMap<String, GapShuffler<String>> mappings;
025    protected StatefulRNG rng;
026
027    /**
028     * Constructs a new Thesaurus with an unseeded RNG used to shuffle word order.
029     */
030    public Thesaurus()
031    {
032        mappings = new LinkedHashMap<>(256);
033        rng = new StatefulRNG();
034    }
035
036    /**
037     * Constructs a new Thesaurus, seeding its RNG (used to shuffle word order) with the next long from the given RNG.
038     * @param rng an RNG that will only be used to get one long (for seeding this class' RNG)
039     */
040    public Thesaurus(RNG rng)
041    {
042        mappings = new LinkedHashMap<>(256);
043        this.rng = new StatefulRNG(rng.nextLong());
044    }
045
046    /**
047     * Constructs a new Thesaurus, seeding its RNG (used to shuffle word order) with shuffleSeed.
048     * @param shuffleSeed a long for seeding this class' RNG
049     */
050    public Thesaurus(long shuffleSeed)
051    {
052        mappings = new LinkedHashMap<>(256);
053        this.rng = new StatefulRNG(shuffleSeed);
054    }
055
056
057    /**
058     * Constructs a new Thesaurus, seeding its RNG (used to shuffle word order) with shuffleSeed.
059     * @param shuffleSeed a String for seeding this class' RNG
060     */
061    public Thesaurus(String shuffleSeed)
062    {
063        mappings = new LinkedHashMap<>(256);
064        this.rng = new StatefulRNG(shuffleSeed);
065    }
066
067    /**
068     * Allows this Thesaurus to find the exact words in synonyms and, when requested, replace each occurrence with a
069     * different word from the same Collection. Each word in synonyms should have the same part of speech, so "demon"
070     * and "devils" should not be in the same list of synonyms (singular noun and plural noun), but "demon" and "devil"
071     * could be (each is a singular noun). The Strings in synonyms should all be lower-case, since case is picked up
072     * from the text as it is being replaced and not from the words themselves. Proper nouns should normally not be used
073     * as synonyms, since this could get very confusing if it changed occurrences of "Germany" to "France" at random and
074     * a character's name, like "Dorothy", to "Anne", "Emily", "Cynthia", etc. in the middle of a section about Dorothy.
075     * The word matching pattern this uses only matches all-letter words, not words that contain hyphens, apostrophes,
076     * or other punctuation.
077     * @param synonyms a Collection of lower-case Strings with similar meaning and the same part of speech
078     * @return this for chaining
079     */
080    public Thesaurus addSynonyms(Collection<String> synonyms)
081    {
082        if(synonyms.isEmpty())
083            return this;
084        rng.setState(CrossHash.hash64(synonyms));
085        GapShuffler<String> shuffler = new GapShuffler<>(synonyms, rng);
086        for(String syn : synonyms)
087        {
088            mappings.put(syn, shuffler);
089        }
090        return this;
091    }
092
093    /**
094     * Allows this Thesaurus to replace a specific keyword, typically containing multiple backtick characters ('`') so
095     * it can't be confused with a "real word," with one of the words in synonyms (chosen in shuffled order). The
096     * backtick is the only punctuation character that this class' word matcher considers part of a word, both for this
097     * reason and because it is rarely used in English text.
098     * @param keyword a word (typically containing backticks, '`') that will be replaced by a word from synonyms
099     * @param synonyms a Collection of lower-case Strings with similar meaning and the same part of speech
100     * @return this for chaining
101     */
102    public Thesaurus addCategory(String keyword, Collection<String> synonyms)
103    {
104        if(synonyms.isEmpty())
105            return this;
106        rng.setState(CrossHash.hash64(synonyms));
107        GapShuffler<String> shuffler = new GapShuffler<>(synonyms, rng);
108        mappings.put(keyword, shuffler);
109        return this;
110    }
111
112    /**
113     * Adds several pre-made categories to this Thesaurus' known categories, but won't cause it to try to replace normal
114     * words with synonyms (only categories, which contain backticks in the name). The keywords this currently knows,
115     * and the words it will replace those keywords with, are:
116     * <br>
117     * <ul>
118     *     <li>"calm`adj`": harmonious, peaceful, pleasant, serene, placid, tranquil, calm</li>
119     *     <li>"calm`noun`": harmony, peace, kindness, serenity, tranquility, calmn</li>
120     *     <li>"org`noun`": fraternity, brotherhood, order, group, foundation</li>
121     *     <li>"org`nouns`": fraternities, brotherhoods, orders, groups, foundations</li>
122     *     <li>"empire`adj`": imperial, princely, kingly, regal, dominant, dynastic, royal, hegemonic, monarchic, ascendant</li>
123     *     <li>"empire`noun`": empire, emirate, kingdom, sultanate, dominion, dynasty, imperium, hegemony, triumvirate, ascendancy</li>
124     *     <li>"empire`nouns`": empires, emirates, kingdoms, sultanates, dominions, dynasties, imperia, hegemonies, triumvirates, ascendancies</li>
125     *     <li>"duke`noun`": duke, earl, baron, fief, lord, shogun</li>
126     *     <li>"duke`nouns`": dukes, earls, barons, fiefs, lords, shoguns</li>
127     *     <li>"duchy`noun`": duchy, earldom, barony, fiefdom, lordship, shogunate</li>
128     *     <li>"duchy`nouns`": duchies, earldoms, baronies, fiefdoms, lordships, shogunates</li>
129     *     <li>"magical`adj`": arcane, enchanted, sorcerous, ensorcelled, magical, mystical</li>
130     *     <li>"holy`adj`": auspicious, divine, holy, sacred, prophetic, blessed, godly</li>
131     *     <li>"unholy`adj`": bewitched, occult, unholy, macabre, accursed, foul, vile</li>
132     *     <li>"forest`adj`": natural, primal, verdant, lush, fertile, bountiful</li>
133     *     <li>"forest`noun`": nature, forest, greenery, jungle, woodland, grove, copse</li>
134     * </ul>
135     * Capitalizing the first letter in the keyword where it appears in text you call process() on will capitalize the
136     * first letter of the produced fake word. Capitalizing the second letter will capitalize the whole produced fake
137     * word. This applies only per-instance of each keyword; it won't change the internally-stored list of words.
138     * @return this for chaining
139     */
140    public Thesaurus addKnownCategories()
141    {
142        for(Map.Entry<String, ArrayList<String>> kv : categories.entrySet())
143        {
144            addCategory(kv.getKey(), kv.getValue());
145        }
146        return this;
147    }
148
149    /**
150     * Adds a large list of words pre-generated by FakeLanguageGen and hand-picked for fitness, and makes them
151     * accessible with a keyword based on the language and any tweaks made to it. The keywords this currently knows:
152     * <br>
153     * <ul>
154     *     <li>"jp`gen`": Imitation Japanese</li>
155     *     <li>"fr`gen`": Imitation French; contains some accented chars</li>
156     *     <li>"gr`gen`": Imitation Greek (romanized)</li>
157     *     <li>"ru`gen`": Imitation Russian (romanized)</li>
158     *     <li>"sw`gen`": Imitation Swahili</li>
159     *     <li>"so`gen`": Imitation Somali</li>
160     *     <li>"en`gen`": Imitation English (not very good on its own)</li>
161     *     <li>"ar`gen`": Imitation Arabic (better); doesn't have accents and should be more readable</li>
162     *     <li>"ar`acc`gen`": Imitation Arabic (worse); has special accents and uses two Greek letters as well</li>
163     *     <li>"hi`gen`": Imitation Hindi (romanized and with accents removed)</li>
164     *     <li>"fn`gen`": Fantasy Names; styled after the possibly-Europe-like names common in fantasy books</li>
165     *     <li>"fn`acc`gen`": Fancy Fantasy Names; the same as "fn`gen`", but with lots of accented chars</li>
166     *     <li>"lc`gen`": Lovecraft; styled after the names of creatures from H.P. Lovecraft's Cthulhu Mythos</li>
167     *     <li>"ru`so`gen`": Mix of imitation Russian (75%) and Somali (25%)</li>
168     *     <li>"gr`hi`gen`": Mix of imitation Greek (50%) and Hindi (accents removed, 50%)</li>
169     *     <li>"sw`fr`gen`": Mix of imitation Swahili (70%) and French (30%)</li>
170     *     <li>"ar`jp`gen`": Mix of imitation Arabic (accents removed, 60%) and Japanese (40%)</li>
171     *     <li>"sw`gr`gen`": Mix of imitation Swahili (60%) and Greek (40%)</li>
172     *     <li>"gr`so`gen`": Mix of imitation Greek (60%) and Somali (40%)</li>
173     *     <li>"en`hi`gen`": Mix of imitation English (60%) and Hindi (accents removed, 40%)</li>
174     *     <li>"en`jp`gen`": Mix of imitation English (60%) and Japanese (40%)</li>
175     *     <li>"so`hi`gen`": Mix of imitation Somali (60%) and Hindi (accents removed, 40%)</li>
176     *     <li>"ru`gr`gen`": Mix of imitation Russian (60%) and Greek (40%)</li>
177     *     <li>"lc`gr`gen`": Mix of Lovecraft-styled names (60%) and imitation Russian (40%)</li>
178     *     <li>"fr`mod`gen`": Imitation French; modified to replace doubled consonants like "gg" with "gsh" or similar</li>
179     *     <li>"jp`mod`gen`": Imitation Japanese; modified to sometimes double vowels from "a" to "aa" or similar</li>
180     *     <li>"so`mod`gen`": Imitation Somali (not really); modified beyond recognition and contains accents</li>
181     * </ul>
182     * Capitalizing the first letter in the keyword where it appears in text you call process() on will capitalize the
183     * first letter of the produced fake word, which is often desirable for things like place names. Capitalizing the
184     * second letter will capitalize the whole produced fake word. This applies only per-instance of each keyword; it
185     * won't change the internally-stored list of words.
186     * @return this for chaining
187     */
188    public Thesaurus addFakeWords()
189    {
190        for(Map.Entry<String, ArrayList<String>> kv : languages.entrySet())
191        {
192            addCategory(kv.getKey(), kv.getValue());
193        }
194        return this;
195    }
196
197    /**
198     * Given a String, StringBuilder, or other CharSequence that should contain words this knows synonyms for, this
199     * replaces each occurrence of such a known word with one of its synonyms, leaving unknown words untouched. Words
200     * that were learned together as synonyms with addSynonyms() will be replaced in such a way that an individual
201     * replacement word should not occur too close to a previous occurrence of the same word; that is, replacing the
202     * text "You fiend! You demon! You despoiler of creation; devil made flesh!", where "fiend", "demon", and "devil"
203     * are all synonyms, would never produce a string that contained "fiend" as the replacement for all three of those.
204     * @param text a CharSequence, such as a String, that contains words in the source language
205     * @return a String of the translated text.
206     */
207    public String process(CharSequence text)
208    {
209        Replacer rep = wordMatch.replacer(new SynonymSubstitution());
210        return rep.replace(text);
211    }
212
213    public String lookup(String word)
214    {
215        if(word.isEmpty())
216            return word;
217        String word2 = word.toLowerCase();
218        if(mappings.containsKey(word2))
219        {
220            String nx = mappings.get(word2).getNext();
221            if(nx.isEmpty())
222                return nx;
223            if(word.length() > 1 && Character.isUpperCase(word.charAt(1)))
224                return nx.toUpperCase();
225            if(Character.isUpperCase(word.charAt(0)))
226            {
227                return Character.toUpperCase(nx.charAt(0)) + nx.substring(1, nx.length());
228            }
229            return nx;
230        }
231        return word;
232    }
233
234    private class SynonymSubstitution implements Substitution
235    {
236        @Override
237        public void appendSubstitution(MatchResult match, TextBuffer dest) {
238            dest.append(lookup(match.group(0)));
239        }
240    }
241
242    public static LinkedHashMap<String, ArrayList<String>> categories = makeLHM(
243            "calm`adj`",
244            makeList("harmonious", "peaceful", "pleasant", "serene", "placid", "tranquil", "calm"),
245            "calm`noun`",
246            makeList("harmony", "peace", "kindness", "serenity", "tranquility", "calm"),
247            "org`noun`",
248            makeList("fraternity", "brotherhood", "order", "group", "foundation", "association", "guild", "fellowship", "partnership"),
249            "org`nouns`",
250            makeList("fraternities", "brotherhoods", "orders", "groups", "foundations", "associations", "guilds", "fellowships", "partnerships"),
251            "empire`adj`",
252            makeList("imperial", "princely", "kingly", "regal", "dominant", "dynastic", "royal", "hegemonic", "monarchic", "ascendant"),
253            "empire`noun`",
254            makeList("empire", "emirate", "kingdom", "sultanate", "dominion", "dynasty", "imperium", "hegemony", "triumvirate", "ascendancy"),
255            "empire`nouns`",
256            makeList("empires", "emirates", "kingdoms", "sultanates", "dominions", "dynasties", "imperia", "hegemonies", "triumvirates", "ascendancies"),
257            "union`noun`",
258            makeList("union", "alliance", "coalition", "confederation", "federation", "congress", "confederacy", "league", "faction"),
259            "union`nouns`",
260            makeList("unions", "alliances", "coalitions", "confederations", "federations", "congresses", "confederacies", "leagues", "factions"),
261            "militia`noun`",
262            makeList("rebellion", "resistance", "militia", "liberators", "warriors", "fighters", "militants", "front", "irregulars"),
263            "militia`nouns`",
264            makeList("rebellions", "resistances", "militias", "liberators", "warriors", "fighters", "militants", "fronts", "irregulars"),
265            "gang`noun`",
266            makeList("gang", "syndicate", "mob", "crew", "posse", "mafia", "cartel"),
267            "gang`nouns`",
268            makeList("gangs", "syndicates", "mobs", "crews", "posses", "mafias", "cartels"),
269            "duke`noun`",
270            makeList("duke", "earl", "baron", "fief", "lord", "shogun"),
271            "duke`nouns`",
272            makeList("dukes", "earls", "barons", "fiefs", "lords", "shoguns"),
273            "duchy`noun`",
274            makeList("duchy", "earldom", "barony", "fiefdom", "lordship", "shogunate"),
275            "duchy`nouns`",
276            makeList("duchies", "earldoms", "baronies", "fiefdoms", "lordships", "shogunates"),
277            "magical`adj`",
278            makeList("arcane", "enchanted", "sorcerous", "ensorcelled", "magical", "mystical"),
279            "holy`adj`",
280            makeList("auspicious", "divine", "holy", "sacred", "prophetic", "blessed", "godly"),
281            "unholy`adj`",
282            makeList("bewitched", "occult", "unholy", "macabre", "accursed", "profane", "vile"),
283            "forest`adj`",
284            makeList("natural", "primal", "verdant", "lush", "fertile", "bountiful"),
285            "forest`noun`",
286            makeList("nature", "forest", "greenery", "jungle", "woodland", "grove", "copse"),
287            "fancy`adj`",
288            makeList("grand", "glorious", "magnificent", "magnanimous", "majestic", "great", "powerful"),
289            "evil`adj`",
290            makeList("heinous", "scurrilous", "terrible", "horrible", "debased", "wicked", "evil", "malevolent", "nefarious", "vile"),
291            "good`adj`",
292            makeList("righteous", "moral", "good", "pure", "compassionate", "flawless", "perfect"),
293            "sinister`adj`",
294            makeList("shadowy", "silent", "lethal", "deadly", "fatal", "venomous", "cutthroat", "murderous", "bloodstained"),
295            "sinister`noun`",
296            makeList("shadow", "silence", "assassin", "ninja", "venom", "poison", "snake", "murder", "blood", "razor"),
297            "blade`noun`",
298            makeList("blade", "knife", "sword", "axe", "stiletto", "katana", "scimitar", "hatchet", "spear", "glaive", "halberd",
299                    "hammer", "maul", "flail", "mace", "sickle", "scythe", "whip", "lance", "nunchaku", "saber", "cutlass", "trident"),
300            "bow`noun`",
301            makeList("bow", "longbow", "shortbow", "crossbow", "sling", "atlatl", "bolas", "javelin", "net", "shuriken", "dagger"),
302            "weapon`noun`",
303            makeList("blade", "knife", "sword", "axe", "stiletto", "katana", "scimitar", "hatchet", "spear", "glaive", "halberd",
304                    "hammer", "maul", "flail", "mace", "sickle", "scythe", "whip", "lance", "nunchaku", "saber", "cutlass", "trident",
305                    "bow", "longbow", "shortbow", "crossbow", "sling", "atlatl", "bolas", "javelin", "net", "shuriken", "dagger"),
306            "musket`noun`",
307            makeList("arquebus", "blunderbuss", "musket", "matchlock", "flintlock", "wheellock", "cannon"),
308            "grenade`noun`",
309            makeList("rocket", "grenade", "missile", "bomb", "warhead", "explosive", "flamethrower"),
310            "rifle`noun`",
311            makeList("pistol", "rifle", "handgun", "firearm", "longarm", "shotgun"),
312            "blade`nouns`",
313            makeList("blades", "knives", "swords", "axes", "stilettos", "katana", "scimitars", "hatchets", "spears", "glaives", "halberds",
314                    "hammers", "mauls", "flails", "maces", "sickles", "scythes", "whips", "lances", "nunchaku", "sabers", "cutlasses", "tridents"),
315            "bow`nouns`",
316            makeList("bows", "longbows", "shortbows", "crossbows", "slings", "atlatls", "bolases", "javelins", "nets", "shuriken", "daggers"),
317            "weapon`nouns`",
318            makeList("blades", "knives", "swords", "axes", "stilettos", "katana", "scimitars", "hatchets", "spears", "glaives", "halberds",
319                    "hammers", "mauls", "flails", "maces", "sickles", "scythes", "whips", "lances", "nunchaku", "sabers", "cutlasses", "tridents",
320                    "bows", "longbows", "shortbows", "crossbows", "slings", "atlatls", "bolases", "javelins", "nets", "shuriken", "daggers"),
321            "musket`nouns`",
322            makeList("arquebusses", "blunderbusses", "muskets", "matchlocks", "flintlocks", "wheellocks", "cannons"),
323            "grenade`nouns`",
324            makeList("rockets", "grenades", "missiles", "bombs", "warheads", "explosives", "flamethrowers"),
325            "rifle`nouns`",
326            makeList("pistols", "rifles", "handguns", "firearms", "longarms", "shotguns"),
327            "tech`adj`",
328            makeList("cyber", "digital", "electronic", "techno", "hacker", "crypto", "turbo", "mechanical", "servo"),
329            "sole`adj`",
330            makeList("sole", "true", "singular", "total", "ultimate", "final"),
331            "light`adj`",
332            makeList("bright", "glowing", "solar", "stellar", "lunar", "radiant", "luminous", "shimmering"),
333            "light`noun`",
334            makeList("light", "glow", "sun", "star", "moon", "radiance", "dawn", "torch"),
335            "light`nouns`",
336            makeList("lights", "glimmers", "suns", "stars", "moons", "torches"),
337            "smart`adj`",
338            makeList("brilliant", "smart", "genius", "wise", "clever", "cunning", "mindful", "aware"),
339            "smart`noun`",
340            makeList("genius", "wisdom", "cunning", "awareness", "mindfulness", "acumen", "smarts", "knowledge"),
341            "bandit`noun`",
342            makeList("thief", "raider", "bandit", "rogue", "brigand", "highwayman", "pirate"),
343            "bandit`nouns`",
344            makeList("thieves", "raiders", "bandits", "rogues", "brigands", "highwaymen", "pirates"),
345            "guard`noun`",
346            makeList("protector", "guardian", "warden", "defender", "guard", "shield", "sentinel", "watchman", "knight"),
347            "guard`nouns`",
348            makeList("protectors", "guardians", "wardens", "defenders", "guards", "shields", "sentinels", "watchmen", "knights"),
349            "rage`noun`",
350            makeList("rage", "fury", "anger", "wrath", "frenzy", "vengeance")
351            ),
352            languages = makeLHM(
353            "lc`gen`",
354            makeList("lahatos", "iatsiltak", "hmimrekaarl", "yixaltaishk", "cthupaxa", "zvroggamraa", "ixaakran"),
355            "jp`gen`",
356            makeList("naimoken", "kishigu", "houdaibo", "souchaya", "aijake", "hyazuran", "pajokke", "sokkimou"),
357            "fr`gen`",
358            makeList("devive", "esiggoi", "afaddouille", "roiquide", "obaploui", "baîmefi", "vêggrôste", "blaçeglè", "bamissecois"),
359            "gr`gen`",
360            makeList("lemabus", "ithonxeum", "etoneoch", "eirkuirstes", "taunonkos", "krailozes", "amarstei", "psorsteomium"),
361            "ru`gen`",
362            makeList("belyvia", "tiuzhiskit", "dazyved", "dabrisazetsky", "shaskianyr", "goskabad", "deblieskib", "neskagre"),
363            "sw`gen`",
364            makeList("mzabandu", "nzaloi", "tamzamda", "anzibo", "jamsala", "latazi", "faazaza", "uzoge", "mbomuta", "nbasonga"),
365            "so`gen`",
366            makeList("daggidda", "xabuumaq", "naadhana", "goquusad", "baxiltuu", "qooddaddut", "mosumyuuc", "uggular", "jaabacyut"),
367            "en`gen`",
368            makeList("thabbackion", "joongipper", "urbigsus", "otsaffet", "pittesely", "ramesist", "elgimmac", "genosont", "bessented"),
369            "fn`gen`",
370            makeList("kemosso", "venzendo", "tybangue", "evendi", "ringamye", "drayusta", "acleutos", "nenizo", "ifelle", "rytoudo"),
371            "fn`acc`gen`",
372            makeList("tánzeku", "nìāfőshi", "ñoffêfès", "áfŏmu", "drĕstishű", "pyeryĕquı", "bėdĕbǽ", "nęìjônne", "mainűthî"),
373            "ar`acc`gen`",
374            makeList("azawiq", "al-ahaluq", "isabzīz", "zūrżuhikari", "īrālať", "ījīqab", "qizifih", "ibn-āħūkū", "šulilfas"),
375            "ar`gen`",
376            makeList("iibaatuu", "wiilnalza", "ulanzha", "jaliifa", "iqaddiz", "waatufaa", "lizhuqa", "qinzaamju", "zuzuri"),
377            "hi`gen`",
378            makeList("maghubadhit", "bhunasu", "ipruja", "dhuevasam", "nubudho", "ghasaibi", "virjorghu", "khlindairai", "irsinam"),
379            "ru`so`gen`",
380            makeList("tserokyb", "zhieziufoj", "bisaskug", "nuriesyv", "gybared", "bableqa", "pybadis", "wiuskoglif", "zakalieb"),
381            "gr`hi`gen`",
382            makeList("takhada", "bepsegos", "ovukhrim", "sinupiam", "nabogon", "umianum", "dhainukotron", "muisaithi", "aerpraidha"),
383            "sw`fr`gen`",
384            makeList("nchaleûja", "soëhusi", "nsavarço", "fambofai", "namyàmse", "mfonsapa", "zalasha", "hiplaîpu", "hœumyemza"),
385            "ar`jp`gen`",
386            makeList("jukkaizhi", "hibiikkiiz", "shomela", "qhabohuz", "isiikya", "akkirzuh", "jalukhmih", "uujajon", "ryaataibna"),
387            "sw`gr`gen`",
388            makeList("ozuxii", "muguino", "nauteicha", "mjixazi", "yataya", "pomboirki", "achuiga", "maleibe", "psizeso", "njameichim"),
389            "gr`so`gen`",
390            makeList("xaaxoteum", "basaalii", "azaibe", "oupeddom", "pseiqigioh", "garkame", "uddoulis", "jobegos", "eqisol"),
391            "en`hi`gen`",
392            makeList("promolchi", "dhontriso", "gobhamblom", "hombangot", "sutsidalm", "dhindhinaur", "megsesa", "skaghinma", "thacebha"),
393            "en`jp`gen`",
394            makeList("nyintazu", "haxinsen", "kedezorp", "angapec", "donesalk", "ranepurgy", "laldimyi", "ipprijain", "bizinni"),
395            "so`hi`gen`",
396            makeList("yiteevadh", "omithid", "qugadhit", "nujagi", "nidogish", "danurbha", "sarojik", "cigafo", "tavodduu", "huqoyint"),
397            "fr`mod`gen`",
398            makeList("egleidô", "glaiemegchragne", "veçebun", "aubudaî", "peirquembrut", "eglecque", "marçoimeaux", "jêmbrégshre"),
399            "jp`mod`gen`",
400            makeList("dotobyu", "nikinaan", "gimoummee", "aanzaro", "ryasheeso", "aizaizo", "nyaikkashaa", "kitaani", "maabyopai"),
401            "so`mod`gen`",
402            makeList("sanata", "ájisha", "soreeggár", "quágeleu", "abaxé", "tedora", "bloxajac", "tiblarxo", "oodagí", "jélebi"),
403            "ru`gr`gen`",
404            makeList("zydievov", "pyplerta", "gaupythian", "kaustybre", "larkygagda", "metuskiev", "vuvidzhian", "ykadzhodna", "paziutso"),
405            "lc`gr`gen`",
406            makeList("fesiagroigor", "gledzhiggiakh", "saghiathask", "sheglerliv", "hmepobor", "riagarosk", "kramrufot", "glonuskiub"));
407}