Javascript: найти ровно 10 слов в дереве префиксов, которые начинаются с заданного префикса

У меня есть дерево (также называемое префиксным деревом). Учитывая префикс, я хочу получить список из десяти слов, начинающихся с префикса.

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

Мой код ниже, я знаю, работает нормально. Каждый узел в дереве имеет свойство children и свойство this_is_the_end_of_a_word. Например, когда вы вставляете «привет», это выглядит так:

три.

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

Мой подход к проблеме был следующим: спускайтесь вниз по дереву префиксов, следуя символам prefix, пока не дойдете до узла, который соответствует последнему символу prefix. Теперь вы должны выполнить DFS на этом узле, отслеживая узлы, которые имеют this_is_the_end_of_a_word === true в списке. Но вы должны прекратить поиск, когда длина вашего списка будет равна 10, и вернуть список.

Я думаю, что мой подход правильный, но у меня возникли проблемы с его реализацией, особенно потому, что я пытаюсь использовать рекурсивную DFS, поэтому я не уверен, как передать «глобальный» список между рекурсивными вызовами. Я знаю, что должен использовать замыкания, но я новичок в javascript и не знаю, как это сделать. Пример того, что я уже пробовал, приведен ниже.

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

var Trie = function() {

    var that = Object.create(Trie.prototype);
    that.children = {}; //mapping: next character -> child nodes
    that.this_is_the_end_of_a_word = false;

    that.insertWord = function(word) {

        var current_node = that;

        for (var i = 0; i < word.length; i++) {
            var c = word[i]
                //if character is not in the trie already, add it
            if (!(c in current_node.children)) {
                current_node.children[c] = Trie();
            }
            //update current_node
            current_node = current_node.children[c];
        };

        //after adding all the chars of the word, 
        //you are at the end of a word
        current_node.this_is_the_end_of_a_word = true;
    }

    that.insertWords = function(words) {
        for (var i = 0; i < words.length; i++) {
            that.insertWord(words[i]);
        }
    }

    that.contains = function(word) {
        //start at the root
        var current_node = that;
        for (var i = 0; i < word.length; i++) {
            var c = word[i];

            //if the word's character isn't a child of the current_node, 
            //the word isn't in the trie
            if (!(c in current_node.children)) {
                return false;
            }
            //move down the trie, update current_node
            current_node = current_node.children[c];
        };
        return current_node.this_is_the_end_of_a_word;
    }

    Object.freeze(that);
    return that;
}

Мой 1-й подход (много ошибок)

num_words_to_go = 10; 
//this global is bad practice; 
//I want to put this as the argument to a closure 
//so it's passed between recursive calls

that.getWords = function(start_node, prefix) {
   console.log(0);
   var words = [];

   //if start node is a word, add it
   if (start_node.this_is_the_end_of_a_word) {
       words.push(start_node);
       num_words_to_go--;
   }

   if (num_words_to_go <= 0 || !start_node.children) {
       return words;
   }

   return start_node.children.forEach(
                              currentValue.getWords(
                                    currentValue, prefix + <character for this child>)); 

   /*I can't think of a nice way to write this without going through all of the children. 
   I know I don't need to, because I only need to find 10 words and get out. 
   This is why I was leaning towards the recursive DFS. 
   */

}

2-й подход: я также нашел пример Python, на который я смотрел: http://v1v3kn.tumblr.com/post/18238156967/roll-your-own-autocomplete-solution-using-tries Я попытался перевести его пример на JavaScript, но все равно что-то не так all_suffixes.

that.all_suffixes = function (prefix){
    results = [];
    if (that.this_is_the_end_of_a_word) results.push(prefix);
    if (!(that.children)) return results;
    if (results.length > 2) return results;
    var callback = function(currentValue, i, array){
        return currentValue.all_suffixes(prefix+array[i]);
    }
    arr = that.children.forEach(callback, that);
        //[child.all_suffixes(prefix + char) for (char, child) in self.children.items()]
    return concat(reduce(concat, arr), results);        
}

 that.autocomplete = function(prefix){
    current_node = that;
    for(var i = 0; i < prefix.length; i++){
        var c = prefix[i];
        //if there is nothing in the trie with this prefix
        if (!(c in current_node.children)){
            return [];
        }
        current_node = current_node.children[c];
    }
    return list(current_node.all_suffixes(prefix))
 }

person user2946797    schedule 24.09.2015    source источник
comment
пожалуйста, взгляните на stackoverflow.com/questions/32595151/   -  person Nina Scholz    schedule 24.09.2015
comment
спасибо, я просмотрел этот пост, но он ищет все слова, которые начинаются с префикса. однако уникальность этой проблемы заключается в том, что мне нужны только 10 слов, начинающихся с данного префикса, а не все.   -  person user2946797    schedule 24.09.2015
comment
Есть ли причина, по которой Javascript имеет отношение к проблеме? Похоже, вы задаете 2 вопроса, как написать/улучшить алгоритм для этого и как это сделать в javascript. Возможно, вы сможете найти похожие ответы на других языках, что вас смущает? Метод решения проблемы или как это реализовать на конкретном языке?   -  person Tai    schedule 24.09.2015


Ответы (1)


В основном я беру вашу модель и применяю новый метод getWords(word[, count]) к классу Trie. Я изменил метод contains, потому что мне также нужны функции getWords. Поэтому я создал новый метод getNode, который возвращает узел, в котором найдено слово или его часть.

Метод getWords сначала ищет слово (часть), а затем перебирает структуру данных. Когда слово найдено, оно помещается в набор результатов. Если длина набора результатов больше или равна требуемой длине, то итерация (отсюда Array.prototype.some) прекращается и рекурсивный вызов fork останавливается.

    that.getWords = function (word, count) {

        function fork(n, w) {

            function child(c) {
                return fork(n.children[c], w + c);
            }

            n.isWord && words.push(w);
            return words.length >= count || Object.keys(n.children).some(child);
        }

        var words = [],
            current_node = that.getNode(word);

        if (current_node) {
            fork(current_node, word);
            return words;
        }
    }

Примечание: я изменил this_is_the_end_of_a_word на isWord.

Ввод

  1. Создайте новый экземпляр Trie.
  2. Вставьте несколько слов для проверки.

Вывод

  1. Проверяет, содержит ли trie 'motor', возвращает false.
  2. Проверяет, содержит ли дерево 'te', возвращает false.
  3. Проверяет, содержит ли дерево 'ten', возвращает true.
  4. Получить все слова, начинающиеся с 'ind' (доступно 8, показано 8).
  5. Получите первые 10 слов, начинающихся с 'in' (доступно 16, показывает 10).
  6. Вся попытка.

var Trie = function () {

    var that = Object.create(Trie.prototype);
    that.children = {}; //mapping: next character -> child nodes
    that.isWord = false;

    that.insertWord = function (word) {
        var current_node = that;
        for (var i = 0; i < word.length; i++) {
            var c = word[i]
            //if character is not in the trie already, add it
            if (!(c in current_node.children)) {
                current_node.children[c] = Trie();
            }
            //update current_node
            current_node = current_node.children[c];
        };

        //after adding all the chars of the word,
        //you are at the end of a word
        current_node.isWord = true;
    }

    that.insertWords = function (words) {
        for (var i = 0; i < words.length; i++) {
            that.insertWord(words[i]);
        }
    }

    that.getNode = function (word) {
        //start at the root
        var current_node = that;
        for (var i = 0; i < word.length; i++) {
            var c = word[i];

            //if the word's character isn't a child of the current_node,
            //the word isn't in the trie
            if (!(c in current_node.children)) {
                return;
            }
            //move down the trie, update current_node
            current_node = current_node.children[c];
        };
        return current_node;
    }

    that.contains = function (word) {
        var current_node = that.getNode(word);
        if (current_node) {
            return current_node.isWord;
        }
        return false;
    }

    that.getWords = function (word, count) {

        function fork(n, w) {

            function child(c) {
                return fork(n.children[c], w + c);
            }

            n.isWord && words.push(w);
            return words.length >= count || Object.keys(n.children).some(child);
        }

        var words = [],
            current_node = that.getNode(word);

        if (current_node) {
            fork(current_node, word);
            return words;
        }
    }

    // freeze does lock the isWord property, which is not required here
    //Object.freeze(that);
    return that;
}

var trie = new Trie();
trie.insertWords([
    'car', 'cool', 'i', 'in', 'indeed', 'independence', 'india', 'indoor', 'induction',
    'industrial', 'industry', 'indwell', 'inferior', 'informal', 'inhale', 'inn',
    'inside', 'instance', 'intrepid', 'of', 'off', 'other', 'tea', 'ted', 'ten',
    'to', 'zoo', 'zoom'
]);
document.write(trie.contains('motor') + '<br>'); // false
document.write(trie.contains('te') + '<br>'); // false
document.write(trie.contains('ten') + '<br>'); // true
document.write('<pre>' + JSON.stringify(trie.getWords('ind'), 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(trie.getWords('in', 10), 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(trie, 0, 4) + '</pre>');

person Nina Scholz    schedule 25.09.2015