11
votes

One-line summary: suggest optimal (lookup-speed/compactness) data structure(s) for a multi-lingual dictionary representing primarily Indo-European languages (list at bottom).

Say you want to build some data structure(s) to implement a multi-language dictionary for let's say the top-N (N~40) European languages on the internet, ranking choice of language by number of webpages (rough list of languages given at bottom of this question). The aim is to store the working vocabulary of each language (i.e. 25,000 words for English etc.) Proper nouns excluded. Not sure whether we store plurals, verb conjugations, prefixes etc., or add language-specific rules on how these are formed from noun singulars or verb stems. Also your choice on how we encode and handle accents, diphthongs and language-specific special characters e.g. maybe where possible we transliterate things (e.g. Romanize German ß as 'ss', then add a rule to convert it). Obviously if you choose to use 40-100 characters and a trie, there are way too many branches and most of them are empty.

Task definition: Whatever data structure(s) you use, you must do both of the following:

  1. The main operation in lookup is to quickly get an indication 'Yes this is a valid word in languages A,B and F but not C,D or E'. So, if N=40 languages, your structure quickly returns 40 Booleans.
  2. The secondary operation is to return some pointer/object for that word (and all its variants) for each language (or null if it was invalid). This pointer/object could be user-defined e.g. the Part-of-Speech and dictionary definition/thesaurus similes/list of translations into the other languages/... It could be language-specific or language-independent e.g. a shared definition of pizza)

And the main metric for efficiency is a tradeoff of a) compactness (across all N languages) and b) lookup speed. Insertion time not important. The compactness constraint excludes memory-wasteful approaches like "keep a separate hash for each word" or "keep a separate for each language, and each word within that language".

So:

  1. What are the possible data structures, how do they rank on the lookup speed/compactness curve?
  2. Do you have a unified structure for all N languages, or partition e.g. the Germanic languages into one sub-structure, Slavic into another etc? or just N separate structures (which would allow you to Huffman-encode )?
  3. What representation do you use for characters, accents and language-specific special characters?
  4. Ideally, give link to algorithm or code, esp. Python or else C. -

(I checked SO and there have been related questions but not this exact question. Certainly not looking for a SQL database. One 2000 paper which might be useful: "Estimation of English and non-English Language Use on the WWW" - Grefenstette & Nioche. And one list of multi-language dictionaries) Resources: two online multi-language dictionaries are Interglot (en/ge/nl/fr/sp/se) and LookWayUp (en<->fr/ge/sp/nl/pt).


Languages to include:

Probably mainly Indo-European languages for simplicity: English, French, Spanish, German, Italian, Swedish + Albanian, Czech, Danish, Dutch, Estonian, Finnish, Hungarian, Icelandic, Latvian, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, Serbo Croat, Slovak, Slovenian + Breton, Catalan, Corsican, Esperanto, Gaelic, Welsh

Probably include Russian, Slavic, Turkish, exclude Arabic, Hebrew, Iranian, Indian etc. Maybe include Malay family too. Tell me what's achievable.

5
Do you handle localised words? (Localized is the WRONG spelling!)Arafangion
I don't know, got any numbers on how they increase the vocabulary size? Are we talking about variants of the same word (e.g. US -ize vs British -ise), or adding entirely new words? I'm only thinking about a working vocabulary (e.g. 25,000 words in English), not a full lexicon. Let's say: somewhat, up to the point that it doesn't detract from the primary task being constructing a multi-lingual structure for N languages.smci
Variations, and is the kind of thing that Mac OS X seems to support by default.Arafangion
@Arafangion: Neither localiSed nor localiZed is wrong because they are locali*ed.Alexey Frunze
@Alex: whatevs. I'm really interested in quality answers to this question... someone out there must have some suggestion.smci

5 Answers

5
votes

I'm not sure whether or not this will work for your particular problem, but here's one idea to think about.

A data structure that's often used for fast, compact representations of language is a minimum-state DFA for the language. You could construct this by creating a trie for the language (which is itself an automaton for recognizing strings in the language), then using of the canonical algorithms for constructing a minimum-state DFA for the language. This may require an enormous amount of preprocessing time, but once you've constructed the automaton you'll have extremely fast lookup of words. You would just start at the start state and follow the labeled transitions for each of the letters. Each state could encode (perhaps) a 40-bit value encoding for each language whether or not the state corresponds to a word in that language.

Because different languages use different alphabets, it might a good idea to separate out each language by alphabet so that you can minimize the size of the transition table for the automaton. After all, if you have words using the Latin and Cyrillic alphabets, the state transitions for states representing Greek words would probably all be to the dead state on Latin letters, while the transitions for Greek characters for Latin words would also be to the dead state. Having multiple automata for each of these alphabets thus could eliminate a huge amount of wasted space.

4
votes

I will not win points here, but some things.

A multi-language dictionary is a large and time-consuming undertaking. You did not talk in detail about the exact uses for which your dictionary is intended: statistical probably, not translating, not grammatical, .... Different usages require different data to be collected, for instance classifying "went" as passed tense.

First formulate your first requirements in a document, and with a programmed interface prototype. Asking data structures before algorithmic conception I see often for complex business logic. One would then start out wrong, risking feature creep. Or premature optimisation, like that romanisation, which might have no advantage, and bar bidrectiveness.

Maybe you can start with some active projects like Reta Vortaro; its XML might not be efficient, but give you some ideas for organisation. There are several academic linguistic projects. The most relevant aspect might be stemming: recognising greet/greets/greeted/greeter/greeting/greetings (@smci) as belonging to the same (major) entry. You want to take the already programmed stemmers; they often are well-tested and already applied in electronic dictionaries. My advise would be to research those projects without losing to much energy, impetus, to them; just enough to collect ideas and see where they might be used.

The data structures one can think up, are IMHO of secondary importance. I would first collect all in a well defined database, and then generate the software used data structures. You can then compare and measure alternatives. And it might be for a developer the most interesting part, creating a beautiful data structure & algorithm.


An answer

Requirement:

Map of word to list of [language, definition reference]. List of definitions.

Several words can have the same definition, hence the need for a definition reference. The definition could consist of a language bound definition (grammatical properties, declinations), and/or a language indepedendant definition (description of the notion).

One word can have several definitions (book = (noun) reading material, = (verb) reserve use of location).

Remarks

As single words are handled, this does not consider that an occuring text is in general mono-lingual. As a text can be of mixed languages, and I see no special overhead in the O-complexity, that seems irrelevant.

So a over-general abstract data structure would be:

Map<String /*Word*/, List<DefinitionEntry>> wordDefinitions;
Map<String /*Language/Locale/""*/, List<Definition>> definitions;

class Definition {
    String content;
}

class DefinitionEntry {
    String language;
    Ref<Definition> definition;
}

The concrete data structure:

The wordDefinitions are best served with an optimised hash map.


Please let me add:

I did come up with a concrete data structure at last. I started with the following.

Guava's MultiMap is, what we have here, but Trove's collections with primitive types is what one needs, if using a compact binary representation in core.

One would do something like:

import gnu.trove.map.*;

/**
 * Map of word to DefinitionEntry.
 * Key: word.
 * Value: offset in byte array wordDefinitionEntries,
 * 0 serves as null, which implies a dummy byte (data version?)
 * in the byte arrary at [0].
 */
TObjectIntMap<String> wordDefinitions = TObjectIntHashMap<String>();
byte[] wordDefinitionEntries = new byte[...]; // Actually read from file.

void walkEntries(String word) {
    int value = wordDefinitions.get(word);
    if (value == 0)
        return;
    DataInputStream in = new DataInputStream(
        new ByteArrayInputStream(wordDefinitionEntries));
    in.skipBytes(value);
    int entriesCount = in.readShort();
    for (int entryno = 0; entryno < entriesCount; ++entryno) {
        int language = in.readByte();
        walkDefinition(in, language); // Index to readUTF8 or gzipped bytes.
    }
}
1
votes

A common solution to this in the field of NLP is finite automata. See http://www.stanford.edu/~laurik/fsmbook/home.html.

0
votes

Easy.

Construct a minimal, perfect hash function for your data (union of all dictionaries, construct the hash offline), and enjoy O(1) lookup for the rest of eternity.

Takes advantage of the fact your keys are known statically. Doesn't care about your accents and so on (normalize them prior to hashing if you want).

0
votes

I had a similar (but not exactly) task: implement a four-way mapping for sets, e.g. A, B, C, D

Each item x has it's projections in all of the sets, x.A, x.B, x.C, x.D;

The task was: for each item encountered, determine which set it belongs to and find its projections in other sets.

Using languages analogy: for any word, identify its language and find all translations to other languages.

However: in my case a word can be uniquely identified as belonging to one language only, so no false friends such as burro in Spanish is donkey in English, whereas burro in Italian is butter in English (see also https://www.daytranslations.com/blog/different-meanings/)

I implemented the following solution:

  • Four maps/dictionaries matching the entry to its unique id (integer)
    AtoI[x.A] = BtoI[x.B] = CtoI[x.C] = DtoI[x.D] = i
    
  • Four maps/dictionaries matching the unique id to the corresponding language

    ItoA[i] = x.A;
    ItoB[i] = x.B;
    ItoC[i] = x.C;
    ItoD[i] = x.D;
    

For each encounter x, I need to do 4 searches at worst to get its id (each search is O(log(N))); then 3 access operations, each O(log(N)). All in all, O(log(N)).

I have not implemented this, but I don't see why hash sets cannot be used for either set of dictionaries to make it O(1).

Going back to your problem: Given N concepts in M languages (so N*M words in total)

My approach adapts in the following way:

M lookup hashsets, that give you integer id for every language (or None/null if the word does not exist in the language). Overlapped case is covered by the fact that lookups for different languages will yield different ids.

For each word, you do M*O(1) lookups in the hash sets corresponding to languages, yielding K<=M ids, identifying the word as belonging to K languages;

for each id you need to do (M-1)*O(1) lookups in actual dictionaries, mapping K ids to M-1 translations each)

In total, O(MKM) which I think is not bad, given your M=40 and K will be much smaller than M in most cases (K=1 for quite a lot of words).

As for storage: NM words + NM integers for the id-to-word dictionaries, and the same amount for reverse lookups (word-to-id);