Эффективная структура данных / алгоритм поиска слов на основе транслитерации - PullRequest
6 голосов
/ 24 сентября 2011

Я ищу эффективную структуру данных / алгоритм для хранения и поиска поиска слов на основе транслитерации (например, в Google do: http://www.google.com/transliterate/, но я не пытаюсь использовать API транслитерации Google).К сожалению, на естественном языке, над которым я пытаюсь работать, не реализован soundex, поэтому я сам по себе.

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

Должен быть лучший способкак это сделать для сложных языков, как, например, работает метод ввода пиньинь?Любое предложение о том, с чего начать?

Заранее спасибо.


Редактировать: Если я правильно понимаю, это предложено @ Dialecticus-

Я хочу транслитерироватьот Language1 , который имеет 3 символа a,b,c до Language2 , который имеет 6 символов p,q,r,x,y,z.Из-за различий в количестве символов, которыми обладает каждый язык, и их телефонами, зачастую невозможно определить сопоставление «один к одному».

Предположим фонетически, что это наша таблица ассоциативных массивов / транслитерации:

a -> p, q
b -> r
c -> x, y, z

У нас также есть допустимые списки слов в простых массивах для Language2 :

...
px
qy
...

Если пользователь вводит ac, возможные комбинации становятся px, py, pz, qx, qy, qzпосле шага транслитерации 1. На шаге 2 мы должны выполнить еще один поиск в допустимом списке слов и должны исключить всех из них, кроме px и qy.


Что я делаю в настоящее времяничем не отличается от вышеуказанного подхода.Вместо создания возможных комбинаций с использованием таблицы транслитерации, я создаю регулярное выражение [pq][xyz] и сопоставляю его с моим действительным списком слов, который обеспечивает вывод px и qy.

I'mстремится узнать, есть ли лучший метод, чем этот.

Ответы [ 2 ]

4 голосов
/ 24 сентября 2011

Насколько я понимаю, у вас есть входная строка S в алфавите (давайте назовем это A1), и вы хотите преобразовать ее в строку S ', которая эквивалентна в другом алфавите A2. На самом деле, если я правильно понимаю, вы хотите сгенерировать список [S'1, S'2, ..., S'n] выходных строк, которые потенциально могут быть эквивалентны S.

Один подход, который приходит на ум, заключается в том, что для каждого слова в списке допустимых слов в A2 генерируется список строк в A1, который соответствует. Используя пример в вашем редактировании, мы имеем

px->ac
qy->ac
pr->ab

(я добавил дополнительное действительное слово pr для ясности)

Теперь, когда мы знаем, какие возможные серии входных символов всегда будут отображаться в правильное слово, мы можем использовать нашу таблицу для построения Trie .

Каждый узел будет содержать указатель на список допустимых слов в A2, которые сопоставляются с последовательностью символов в A1, которые образуют путь от корня Trie до текущего узла.

Таким образом, для нашего примера три будет выглядеть примерно так

                                  Root (empty)
                                    | a
                                    |
                                    V
                              +---Node (empty)---+
                              | b                | c
                              |                  |
                              V                  V
                           Node (px,qy)         Node (pr)      

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

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

Использование Trie также обеспечивает то преимущество, что вы также можете использовать его для поиска списка всех допустимых слов, которые могут быть сгенерированы путем добавления большего количества символов в конец ввода - то есть совпадения префикса. Например, если вводится с входным символом «a», мы можем использовать три для поиска всех допустимых слов, которые могут начинаться с «a» («px», «qr», «py»). Но сделать это не так быстро, как найти точное совпадение.

Вот быстрый взлом решения (на Java):

import java.util.*;

class TrieNode{
    // child nodes - size of array depends on your alphabet size,
    // her we are only using the lowercase English characters 'a'-'z'
    TrieNode[] next=new TrieNode[26];
    List<String> words;

    public TrieNode(){
        words=new ArrayList<String>();
    }
}

class Trie{
    private TrieNode root=null;

    public void addWord(String sourceLanguage, String targetLanguage){
        root=add(root,sourceLanguage.toCharArray(),0,targetLanguage);
    }

    private static int convertToIndex(char c){ // you need to change this for your alphabet
        return (c-'a');
    }

    private TrieNode add(TrieNode cur, char[] s, int pos, String targ){
        if (cur==null){
            cur=new TrieNode();
        }
        if (s.length==pos){
            cur.words.add(targ);
        }
        else{

            cur.next[convertToIndex(s[pos])]=add(cur.next[convertToIndex(s[pos])],s,pos+1,targ);
        }
        return cur;
    }

    public List<String> findMatches(String text){
        return find(root,text.toCharArray(),0);

    }

    private List<String> find(TrieNode cur, char[] s, int pos){
        if (cur==null) return new ArrayList<String>();
        else if (pos==s.length){
            return cur.words;
        }
        else{
            return find(cur.next[convertToIndex(s[pos])],s,pos+1);
        }
    }
}

class MyMiniTransliiterator{
    public static void main(String args[]){
        Trie t=new Trie();
        t.addWord("ac","px");
        t.addWord("ac","qy");
        t.addWord("ab","pr");

        System.out.println(t.findMatches("ac")); // prints [px,qy]
        System.out.println(t.findMatches("ab")); // prints [pr]
        System.out.println(t.findMatches("ba")); // prints empty list since this does not match anything
    }
}

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

2 голосов
/ 24 сентября 2011

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

Лучшая структура для таблицы транслитерации - это своего рода ассоциативный массив , вероятно, использующий хеш-таблицы . В C ++ есть std::unordered_map, а в C # вы бы использовали Dictionary.

...