Приоритизация результатов

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

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


Базовый порядок сортировки

По умолчанию Awesomplete использует внутреннюю функцию сортировки, которая сравнивает позиции вхождения строки запроса в элемент списка.

Логика выглядит упрощённо следующим образом:

  1. Совпадения с началом строки имеют наивысший приоритет
  2. Далее идут совпадения, где запрос встречается внутри строки
  3. Затем — более “слабые” совпадения, если они допускаются фильтром

Условная реализация стандартного подхода может быть представлена так:

Awesomplete.prototype.sort = function(a, b) {
    return (a.indexOf(this.input.value) > -1 ? 0 : 1)
         - (b.indexOf(this.input.value) > -1 ? 0 : 1);
};

Хотя реальная реализация более аккуратна, ключевая идея сохраняется: позиция совпадения влияет на порядок.


Переопределение сортировки как основа приоритизации

Awesomplete предоставляет возможность заменить сортировку через параметр sort. Это основной инструмент управления приоритетами.

new Awesomplete(input, {
    list: ["javascript", "java", "python", "php"],
    sort: function(a, b) {
        return customScore(a) - customScore(b);
    }
});

Функция сортировки должна возвращать:

  • отрицательное значение, если a выше b
  • положительное, если b выше a
  • 0, если равны по приоритету

Это позволяет полностью контролировать порядок выдачи.


Приоритет по позиции совпадения

Наиболее распространённая стратегия — учитывать индекс вхождения строки запроса.

function positionScore(item, query) {
    const index = item.toLowerCase().indexOf(query.toLowerCase());
    return index === -1 ? 1000 : index;
}

Использование:

new Awesomplete(input, {
    list: items,
    sort: function(a, b) {
        const q = this.input.value.toLowerCase();
        return positionScore(a, q) - positionScore(b, q);
    }
});

Чем меньше индекс, тем выше приоритет.


Усиление точных совпадений

В интерфейсах автодополнения часто требуется выделять полные совпадения как абсолютно приоритетные.

Добавляется дополнительное правило:

  • точное совпадение → максимальный приоритет
  • начинается с запроса → высокий приоритет
  • содержит внутри → средний приоритет
function score(item, query) {
    item = item.toLowerCase();
    query = query.toLowerCase();

    if (item === query) return 0;
    if (item.startsWith(query)) return 1;

    const pos = item.indexOf(query);
    return pos === -1 ? 1000 : 10 + pos;
}

Сортировка:

sort: (a, b) => score(a, this.input.value) - score(b, this.input.value)

Такой подход делает поведение предсказуемым: наиболее релевантные элементы всегда оказываются наверху.


Взвешенные списки (weight-based prioritization)

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

Пример структуры данных:

const list = [
    { label: "JavaScript", weight: 10 },
    { label: "Java", weight: 8 },
    { label: "Python", weight: 9 }
];

Сортировка учитывает два фактора: релевантность и вес.

function score(item, query) {
    const text = item.label.toLowerCase();
    const q = query.toLowerCase();

    let relevance = text.indexOf(q);
    if (relevance === -1) relevance = 1000;

    const weightPenalty = 10 - item.weight;

    return relevance + weightPenalty;
}

Итоговый порядок начинает учитывать не только текст, но и приоритет домена данных.


Комбинированная модель приоритизации

На практике используется многокомпонентная модель оценки:

  • позиция совпадения
  • тип совпадения (точное / префикс / подстрока)
  • вес элемента
  • длина строки (короткие часто выше)
  • частота использования (если доступна)

Пример расширенной функции:

function advancedScore(item, query) {
    const text = item.label.toLowerCase();
    const q = query.toLowerCase();

    let score = 0;

    if (text === q) score -= 100;
    else if (text.startsWith(q)) score -= 50;

    const pos = text.indexOf(q);
    score += pos === -1 ? 1000 : pos;

    score += text.length * 0.01;

    if (item.weight) {
        score -= item.weight * 2;
    }

    return score;
}

Такой подход позволяет гибко управлять выдачей даже в сложных интерфейсах.


Стабилизация сортировки

Приоритизация часто сталкивается с проблемой нестабильного порядка, когда элементы с одинаковым score “прыгают” при каждом вводе.

Для решения добавляется вторичный критерий сортировки:

sort: function(a, b) {
    const q = this.input.value;

    const sa = score(a, q);
    const sb = score(b, q);

    if (sa === sb) {
        return a.localeCompare(b);
    }

    return sa - sb;
}

Это обеспечивает детерминированный порядок отображения.


Отключение сортировки и сохранение исходного порядка

В некоторых сценариях приоритет задаётся уже порядком массива. Например, когда backend возвращает отсортированный список.

Awesomplete позволяет отключить сортировку:

new Awesomplete(input, {
    list: items,
    sort: false
});

В этом режиме библиотека сохраняет исходный порядок элементов после фильтрации. Это важно при серверной ранжировке, где клиентская логика должна быть минимальной.


Приоритизация через предобработку списка

Иногда более эффективно не переопределять сортировку, а заранее перестроить список.

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

function reorderList(list, query) {
    const q = query.toLowerCase();

    const startsWith = [];
    const contains = [];

    list.forEach(item => {
        const text = item.toLowerCase();
        if (text.startsWith(q)) {
            startsWith.push(item);
        } else {
            contains.push(item);
        }
    });

    return [...startsWith, ...contains];
}

Затем передача в Awesomplete:

input.addEventListener("input", function() {
    awesomplete.list = reorderList(originalList, this.value);
});

Этот подход снижает нагрузку на sort и упрощает логику библиотеки.


Приоритизация в сложных структурах данных

Когда элементы списка — не строки, а объекты, сортировка усложняется.

const list = [
    { label: "React", category: "frontend", popularity: 10 },
    { label: "Node.js", category: "backend", popularity: 9 }
];

Приоритет может зависеть от контекста:

  • если пользователь вводит “re” → frontend важнее
  • если ввод “node” → backend-элементы сдвигаются вверх

Пример контекстной логики:

function contextualScore(item, query) {
    let score = advancedScore(item, query);

    if (query.startsWith("re") && item.category === "frontend") {
        score -= 20;
    }

    if (query.startsWith("node") && item.category === "backend") {
        score -= 20;
    }

    score -= item.popularity;

    return score;
}

Баланс между фильтрацией и приоритизацией

Важно различать два этапа:

  • filter — определяет, попадёт ли элемент в список
  • sort — определяет его позицию внутри списка

Awesomplete позволяет переопределять оба, но приоритизация должна оставаться в sort. Перегрузка фильтра логикой ранжирования приводит к ухудшению читаемости и снижению гибкости.

Правильная модель:

  • filter: грубое исключение нерелевантных элементов
  • sort: точное ранжирование оставшихся

Итоговая архитектура приоритизации

Типичная промышленная схема работы с Awesomplete выглядит следующим образом:

  1. Получение исходного списка (локального или серверного)
  2. Фильтрация по минимальному совпадению
  3. Вычисление score для каждого элемента
  4. Сортировка по score
  5. Вторичная стабилизация через localeCompare
  6. Отображение результата

Такой подход обеспечивает управляемое поведение автодополнения при любых объёмах данных и любых правилах доменной логики.