Как я могу прочитать эту структуру Radix Tree, чтобы определить вероятность следующей строки?

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

Структура, которую я создаю для записи прошлых входных данных пользователя, представляет собой модифицированное дерево оснований (также известное как Патрисия Три):

Ввод: "hey"

{
    "h": {
        "value": "h",
        "count": 1,
        "followables": {
            "e": {
                "value": "e",
                "count": 1,
                "followables": {
                    "y": {
                        "value": "y",
                        "count": 1,
                        "followables": {}
                    }
                }
            }
        }
    }
}

Эта структура данных построена идеально, и я думаю, что это лучшая структура для достижения описанной цели. Моя проблема заключается в функции для чтения данных Radix Tree, чтобы определить 3 наиболее вероятных слова для данного ввода. Например, в приведенных выше данных, если пользователь вводит «h», функция угадывания должна возвращать такой объект:

guess : {
   1 : "hey",
   2 : "",
   3 : ""
}

Итак, вот мой код/прогресс:

Обучение — возьмите завершенную входную строку и организуйте комбинацию в Основное дерево (brain):

function learn(message, brain) {
    if (message.length == 0) return {}; // or do something else
    var ch = message[0]; // get the first character
    if (!brain[ch]) { // create new node when not exists
        brain[ch] = {
            value: ch,
            count: 1,
            followables: {}
        };
    } else { // increment count when exist
        brain[ch].count += 1;
    }
    var substr = message.substring(1); // remove first character
    if (substr) { // do it for the remaining substring
        brain[ch].followables = learn(substr, brain[ch].followables);
    } else {
        renderData();
    }
    return brain;
}

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

Угадай – возьмите "выученные" строковые данные и сделайте 3 предположения о том, какое слово может быть введено пользователем:

function guess(progress, brain) {
    console.log("Guessing based on: " + progress);
    var guesses = {
        0: "",
        1: "",
        2: ""
    }
    var firstChar = progress[0]; 
    if (brain[firstChar]) {
        var step = brain[firstChar];
        for (var i = 0; i < progress.length; i++) {
            var char = progress[i];
            if (step.followables[char]) {
                step = step.followables[char];
                if (i == progress.length) {
                    var guesses = nextStrings(step.followables);
                    renderGuesses(guesses);
                }
            } else {
                renderGuesses(guesses);
            }
        }
    } else {
        renderGuesses(guesses);
    }
}

function renderGuesses(guesses) {
    console.log(guesses);
    $('#guess-1').text(guesses[0]);
    $('#guess-2').text(guesses[1]);
    $('#guess-3').text(guesses[2]);
}

function nextStrings(followables) {
    console.log('Searching for next string...');
    var results;
    if (followables.length > 0) {
        results = chooseRoutes(followables);
    } else {
        results = {
            0: "",
            1: "",
            2: ""
        }
    }
    console.log(result);
    return result;
}

function chooseRoutes(followables) {
    var results = {
        0: {
            value: "",
            count: 0
        },
        1: {
            value: "",
            count: 0
        },
        2: {
            value: "",
            count: 0
        }
    };
    for (var i = 0; i < followables.length; i++) {
        var count = followables[i].count;
        if (count > results[0].count) {
            results[0].value = followStr(followables[i], "");
        } else if (count > results[1].count) {
            results[1].value = followStr(followables[i], "");
        } else if (count > results[2].count) {
            results[2].value = followStr(followables[i], "");
        }
    }
    console.log(results);
    return results;
}

function followStr(followables, str) {
    var guess = {
        value: "",
        count: 0
    };
    for (var i = 0; i < followables.length; i++) {
        if (followables[i].count > guess.count) {
            guess = followables[i];
        }
    }
    followables = guess.followables;
    if (guess.value != " ") {
        str += guess;
        followStr(followables, str);
    } else {
        console.log(str);
        return str;
    }
}

Примечание. Хотя поиск нечеткой строки в словаре является более распространенным подходом к этому, метод обучения — это отличный способ адаптировать предположения к стилю письма/сообщения пользователя и поддерживать нестандартные пользовательские словарь ("heyy", "sup", ":P", "lol") - результаты этих догадок можно комбинировать (и иметь приоритет над) стандартными результатами словаря.


person Community    schedule 16.02.2015    source источник
comment
С субъективной точки зрения, я на самом деле немного разочарован своей неспособностью построить эту функцию guess. Является ли функция/общая цель сложной/продвинутой, или мне просто нужно больше практики? Я изучал JS в течение последних 2-3 лет старшей школы, и мне любопытно узнать, смогу ли я на данный момент пройти через что-то подобное...   -  person    schedule 16.02.2015
comment
Это не особенно простой алгоритм для реализации, но взгляните на stackoverflow.com/ questions/2901831/algorithm-for-autocomplete Вам, вероятно, было бы намного проще, если бы у вас была база данных слов для сопоставления.   -  person Qantas 94 Heavy    schedule 16.02.2015
comment
@ Qantas94Heavy, конечно, я бы, вероятно, хотел объединить это с типичным поиском нечетких строк на основе словаря в производственной реализации. Моя цель здесь состоит в том, чтобы сосредоточиться на вводе пользователя, который хорошо угадывает распространенные, но неправильные строки, такие как u, :P, ‹3, sup, heyy, bro и т. д. Поиск по словарю будет использоваться в производстве, чтобы заполнить, где для догадки не существует комбинаций.   -  person    schedule 16.02.2015
comment
Хм, ваше дерево примеров не является настоящим деревом, оно не сжато? И что именно представляет собой .count?   -  person Bergi    schedule 19.02.2015
comment
@Bergi Возможно, нет - я не особо разбирался в фундаментальной идее, лежащей в основе попытки, но она структурирована как одна и следует той же идее, за исключением, возможно, сжатия, о котором вы говорите. И count представляет количество раз, когда комбинация была записана, таким образом записывая вероятность.   -  person    schedule 19.02.2015
comment
@MediaWebDev: Но тогда счетчик должен храниться только в листьях? Показанное вами дерево представляет собой 1x h, 1x he, 1x hey   -  person Bergi    schedule 19.02.2015
comment
@Bergi Нет, вы упустили один фактор: как проверить вероятность различных возможностей, не спускаясь по всей вершине дерева, чтобы проверить все вероятности слов. Сохраняя вероятности для всех комбинаций, когда пользователь вводит свою первую букву h, мы можем сразу же проследить по дереву, чтобы узнать, что его вторая буква, скорее всего, e, не спускаясь по всей ветке и не проверяя все h слова, чтобы наконец выяснить, что строка hello скорее всего.   -  person    schedule 19.02.2015
comment
@ Берги видишь? когда набирается h, из 100 различных комбинаций, которые могут следовать за ним, мы можем сразу знать, что нужно следовать ветвям e a o только для наших 3 предположений, потому что эти вероятности хранятся на каждом уровне.   -  person    schedule 19.02.2015
comment
Хм, так вы хотите получить предположение только для следующей буквы или для всего слова?   -  person Bergi    schedule 19.02.2015
comment
@Bergi, основываясь на текущих набранных буквах, я хочу угадать полное предполагаемое слово (опять же, это может быть не настоящее слово). Основываясь на вашем предложении хранить количество только полных слов, чтобы проверить вероятность этих слов, мы должны искать все дерево до точки первого пробела в каждой ветви. Это просто не имеет смысла для меня. Возможно, вы могли бы предложить свой подход в ответе, я могу неправильно понять ваше намерение.   -  person    schedule 19.02.2015
comment
@MediaWebDev: хорошо; поэтому хранение максимумов поддерева в его корне - это нормально. Тем не менее, я ожидаю некоторого дополнительного значения счетчика, которое есть только на (листовых) узлах, чтобы вы могли различать счетчики hi, high и hifi.   -  person Bergi    schedule 19.02.2015


Ответы (1)


Структура, которую вы использовали для словаря, неверна, она должна содержать массив(ы) объектов. Например, после того, как вы введете эти слова:

hi
hi
hi
hi
hi
hey
hello
hella

структура должна быть:

history: [{
    letter: "h",
    count: 8,
    followables: [{
        letter: "e",
        count: 3,
        followables: [{
            letter: "y",
            count: 1,
            followables: []
        }, {
            letter: "l",
            count: 2,
            followables: [{
                letter: "l",
                count: 2,
                followables: [{
                    letter: "o",
                    count: 1,
                    followables: []
                }, {
                    letter: "a",
                    count: 1,
                    followables: []
                }]
            }]
        }]
    }, {
        letter: "i",
        count: 5,
        followables: []
    }]
}]

То, как вы создаете и храните историю (я бы использовал localStorage), мне не было интересно. Основное внимание уделялось рекурсивным функциям, которые копаются глубоко внутри дерева, чтобы получить предложения. Этот получает окончательное followables для данного слова:

findChildren: function (node, depth) {
    /* Traverse history for current word, there is only one path */
    for (k in node) {
        if (node[k].letter == app.progress[depth]) {
            if (depth + 1 == app.progress.length) {
                /* Found it, hope it has followables */
                return node[k].followables;
            } else {
                /* Must go deeper... */
                return app.findChildren(node[k].followables, depth + 1);
            };
        };
    };
    /* No results */
    return false;
}

а второй (getWord) создает предложения:

countWordsFromNode: function (node) {
    for (i in node) {
        if (node[i].followables.length) {
            app.countWordsFromNode(node[i].followables);
        } else {
            app.totalInNode++;
        };
    };
},
getWord: function (node, limit) {
    /* First sort by count */
    var sorted = node.sort(function (n1, n2) {
        return n2.count - n1.count;
    });
    for (k in sorted) {
        app.guesses[app.totalFound].word += sorted[k].letter;
        if (sorted[k].followables.length) {
            app.totalInNode = 0;
            app.countWordsFromNode(sorted[k].followables);
            for (m = 1; m < app.totalInNode; m++) {
                if ((app.totalFound + m) < limit) {
                    app.guesses[app.totalFound + m].word += sorted[k].letter;
                };
            };
            app.getWord(sorted[k].followables, limit);
        } else {
            /* End of word */
            app.totalFound++;
        };
        if (app.totalFound >= limit) {
            /* Found all suggestions */
            break;
        };
    };
}

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

guesses: [
    {
        element: $('#guess-1'),
        word: ''
    },
    {
        element: $('#guess-2'),
        word: ''
    },
    {
        element: $('#guess-3'),
        word: ''
    }
]

Изменить: исправлена ​​ошибка с добавлением букв справа.

person skobaljic    schedule 19.02.2015
comment
Можете ли вы объяснить, почему используются массивы, а не объекты? Мне кажется, что теперь вы должны искать массивы «грубой силой» вместо того, чтобы использовать тот факт, что объекты организованы в хэш-карты. Я просто не понимаю, почему при сохранении всех элементов в виде свойств объекта, доступ к которым осуществляется мгновенно, поиск кажется экспоненциально более эффективным. Эта структура данных может быть потенциально массивной. Возможно, я ошибаюсь, я занимаюсь этим всего пару лет, так что не стесняйтесь меня поправлять. - person ; 19.02.2015
comment
Если вы сравните новую структуру здесь с теми же данными, которые у вас были, вы увидите, что она лучше, потому что не является избыточным и более интуитивным: после одной буквы вы ожидаете массив букв. Кроме того, javascript sort имеет решающее значение для получения наиболее часто встречающейся буквы в этом массиве - без него вам пришлось бы создавать новую (до сих пор я не видел сортировки объектов без массивов). - person skobaljic; 19.02.2015
comment
Да, и кстати, большое спасибо за ваши усилия - ваш метод может быть идеальным - он определенно работает (+1). Мне просто интересно узнать, эффективно ли это решение. - person ; 19.02.2015
comment
Я могу найти/создать ошибку в своем алгоритме, мне придется ее исправить. - person skobaljic; 19.02.2015
comment
Я исправил единственную ошибку, которую смог найти. Надеюсь, кто-то сможет оптимизировать этот код (и добавить функцию хранения), это может быть очень полезно. - person skobaljic; 19.02.2015
comment
@skobaljic: я немного не понимаю, что вы имеете в виду под неправильным. - person Robert Harvey; 20.02.2015
comment
Неверно в том смысле, что не обеспечивает работоспособности структуры данных. Вы не можете наблюдать только саму структуру, потому что она не является независимой, и нам нужно создавать методы и получать от нее соответствующие данные. Я использовал массивы, потому что знал, что должен их сортировать, но если бы history были отсортированы в первую очередь, мы могли бы использовать какую-то другую форму структуры данных. - person skobaljic; 20.02.2015