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

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

Возможно, вы видите, что между словами нет корреляции, очевидно, потому что не используется машинное обучение, но основная цель здесь - иметь возможность завершить незаконченное слово (независимо, то есть независимо от того, что было набрано ранее).

Что ж, более чем завершение. Мне было больше любопытно, как работает back-text (прозрачный призрачный текст, завершающий ваш запрос) и как мне добавить свое предложение слова в само поле ввода.

Список слов отсюда. Репозиторий, состоящий из слов здесь.

Чтобы получить более точные предложения, я использовал показатель популярности, основанный на частоте использования английских слов, найденных здесь. (репозиторий здесь). Единственная проблема в том, что там всего около 10к слов, что неплохо!

Предварительные требования перед тем, как перейти к разделу кода:

  • Знание синтаксиса JavaScript ES6 (классы, async / await)
  • Структура данных Trie
  • Добавление слушателей событий в DOM
  • Знание о дребезге. Если не читать здесь.

А теперь давайте продолжим и закодируем это!

Шаг 1. Структура данных

Какую структуру данных мы выбираем и почему? Учитывая, что наш набор слов может быть действительно огромным (на данный момент всего около 102 тысяч), линейный поиск префиксов будет невозможен и нарушит работу пользователя с вашим приложением.

Хорошо известной структурой данных, используемой для поиска и сопоставления строк, является дерево Trie. Здесь важно отметить, что нужно следить за ограничением места, потому что в худшем случае, учитывая, что наши слова имеют только строчные буквы от a до z, могут потребляться примерно:

O (26 * средняя_длина_слов * число_слов)

[в зависимости от реализации вашего дерева]

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

O (длина_запроса)

Давайте закодируем это:

Мы создадим Trie класс, состоящий из методов для add новых слов и выработки предложений.

Давайте посмотрим, как добавить новые слова:

Давайте посмотрим на структуру более внимательно:

Рассмотрим слова ant & and, добавленные к дереву, вот как это будет выглядеть:

Затем нам нужен метод для поиска слова в дереве:

Затем мы будем использовать указанную выше find функцию для создания предложений:

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

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

Это все о кодировании нашей структуры данных, давайте теперь воспользуемся ее отображением предложений!

Давайте воспользуемся этим базовым HTML-кодом для визуализации поля ввода:

Я не включил сюда стили (вы можете увидеть скрипку здесь, чтобы просмотреть весь исходный код).

ЗАМЕТКА:

Имена файлов в приведенном выше gists не следует понимать буквально, у нас есть только 2 файла - index.html и index.js, весь JS-код находится в самом этом файле.

Теперь в нашем файле index.js, который также содержит наш класс Trie, мы добавим прослушиватели событий для обработки ввода в нашу панель поиска.

Двигаясь дальше, предполагая, что вы знаете, что такое debounce (короче говоря, наша main() функция вызывается, когда пользователь не набирает 250ms).

Причина добавления противодействия нашему main() методу:

Метод main() отвечает за извлечение предложений каждый раз, когда изменяется текст в строке поиска, но пользователь хочет, чтобы предложение появлялось только тогда, когда пользователь ожидает короткое время (200–250 мс) во время набора текста. Это не только улучшает взаимодействие с пользователем, но и сокращает количество ненужных вызовов функций метода main().

Давайте посмотрим на функцию main():

Здесь популярность - это мера частоты употребления слов в целом, например, слово the обычно встречается чаще, чем xylophone, верно?

Итак, метод getBestMatch() просто перебирает предложения и выбирает слово с наибольшей частотой.

Давайте разберемся в коде со схемой:

Вы можете видеть здесь, ghost, который является предложением, представляет собой не что иное, как элемент span, расположенный на расстоянии, равном ширине содержимого вашего запроса!

Как определить ширину содержания?

Сделайте еще один div с невидимым где-нибудь в DOM, в который вы можете записать содержимое запроса и измерить clientWidth!

HTML:
<div id='fake-div' class='div'></div>
JS:
const $fakeDiv = document.getElementById('fake-div');
const query = e.target.textContent.toLowerCase();
$fakeDiv.innerText = query;
CONTENT WIDTH = $fakeDiv.clientWidth

Просто правильно! Теперь просто поместите призрак $span в нужное место ...

Поскольку призрак $span должен быть похож на ввод текста в $input div, нам нужно имитировать некоторые свойства CSS $input div, а чтобы получить свойства CSS $input div, мы можем сделать это:

Теперь, чтобы установить style.left из $span, мы просто делаем:

LINE 33 : (main.js) GIST
$span.style.left = r.left + $fakeDiv.clientWidth + 'px';

Осталось только одно! Заполните строку ввода, если пользователь хочет использовать данное предложение, например, если пользователь нажимает клавишу СТРЕЛКА ВПРАВО.

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

Вы можете поиграть с исходным кодом здесь.

И, как всегда, дайте мне знать в комментариях, если вы обнаружите что-то неправильное или если есть что-то, что вы хотите добавить! Ваше здоровье! 🍻