Прямой ответ
Пользователь вводит кириллический запрос, не переключив раскладку, и отправляет «yjnebr» вместо «ноутбук». Расстояние между такими строками велико, поэтому нечёткий поиск не помогает: требуется преобразование по таблице соответствия клавиш.
EN = "qwertyuiop[]asdfghjkl;'zxcvbnm,."
RU = "йцукенгшщзхъфывапролджэячсмитьбю."
MAP = dict(zip(EN, RU)) | dict(zip(RU, EN))
def switch_layout(s: str) -> str:
return "".join(MAP.get(c, c) for c in s.lower())
switch_layout("yjnebr") # "ноутбук"
switch_layout("ноутбук") # "yjnbye"
Преобразование выполняется попарно: латинский символ заменяется кириллическим с той же клавиши и наоборот. Таблица покрывает только клавиши с буквами, поэтому цифры и знаки остаются без изменения.
Транслитерация решает другую задачу — замену букв одного алфавита сочетаниями другого:
| Задача | Пример |
|---|---|
| Смена раскладки | yjnebr → ноутбук |
| Транслитерация | noutbuk → ноутбук |
| Обратная транслитерация | ноутбук → noutbuk |
Различие существенно: при смене раскладки соответствие взаимно однозначно и определяется клавишей, а при транслитерации одно кириллическое письмо может записываться разными способами — «ю» как yu, ju либо û.
Нормализация Unicode выполняется до сравнения строк, поскольку один видимый символ может быть представлен разными последовательностями кодов; о кодировках — в статье про Unicode в Python.
import unicodedata
unicodedata.normalize("NFC", text) # композиция: ё как один символ
unicodedata.normalize("NFD", text) # декомпозиция: буква + диакритика
Где применять преобразование раскладки
Преобразование выполняется либо при разборе запроса, либо при индексации, и выбор определяет объём индекса.
При разборе запроса. Запрос выполняется в двух вариантах — исходном и преобразованном, а результаты объединяются. Индекс не увеличивается, но каждый запрос выполняется дважды.
При индексации. В индекс добавляется вариант в другой раскладке. Объём индекса растёт, а запрос выполняется один раз.
На клиенте. Интерфейс определяет неверную раскладку и предлагает исправление. Сервер не меняется, но преобразование зависит от реализации интерфейса.
Распространённый подход — выполнение обоих запросов и объединение результатов с понижением веса преобразованного варианта:
{
"query": {
"bool": {
"should": [
{"match": {"title": {"query": "yjnebr", "boost": 2}}},
{"match": {"title": {"query": "ноутбук"}}}
],
"minimum_should_match": 1
}
}
}
Исходная форма получает больший вес, поэтому при совпадении обоих вариантов документ с прямым совпадением остаётся выше.
Определение неверной раскладки выполняется эвристически: строка состоит преимущественно из символов одного алфавита, но не содержит слов этого языка. Проверка по словарю надёжнее, но требует словаря.
Отдельный случай — смешанный ввод, когда часть слова набрана в одной раскладке, а часть в другой. Преобразование всей строки не даёт результата, поэтому применяется посимвольный анализ, который на практике используется редко из-за низкой частоты таких опечаток.
Таблица соответствия и её границы
Таблица клавиш покрывает буквы основного алфавита, но три группы символов требуют отдельного решения.
Цифры и знаки препинания. Клавиши цифр совпадают в обеих раскладках, а знаки отличаются: точка с запятой на клавише «ж» в русской раскладке соответствует точке с запятой в латинской, но символ «.» и «/» распределены иначе. Таблица составляется по фактической раскладке, а не по предположению.
Верхний регистр. Преобразование выполняется после приведения к нижнему регистру, поэтому таблица составляется для одного регистра. Обработка заглавных букв отдельно удваивает таблицу без пользы, поскольку поиск нечувствителен к регистру.
Буквы, отсутствующие в другой раскладке. Русская «ъ» и «ь» не имеют соответствия в латинской раскладке на отдельной клавише, поэтому при преобразовании в латиницу остаются без изменения. Обратное преобразование таких символов невозможно, и запрос, содержащий их, не восстанавливается.
Практическое следствие: таблица проверяется по описанию раскладки, а результат преобразования — на наборе реальных запросов. Запрос, который после преобразования не находится, указывает на пропущенное соответствие либо на другую причину пустой выдачи.
Отдельно стоит различать раскладку и транслитерацию в интерфейсе. Подсказка «найти: ноутбук» при вводе «yjnebr» полезна, а автоматическая замена без отображения исходной формы лишает пользователя контроля, поэтому отображается обе формы.
Практика: обработка ввода в неверной раскладке
1. Нормализуйте Unicode до сравнения. Разные представления одного символа приводят к несовпадению строк, которые выглядят одинаково.
2. Приводите к нижнему регистру до преобразования. Таблица соответствия составляется для одного регистра, поэтому преобразование после приведения упрощает таблицу.
3. Выполняйте оба варианта запроса. Исходная форма сохраняет преимущество, а преобразованная находит документы при неверной раскладке.
4. Понижайте вес преобразованного варианта. Без понижения документ с совпадением по преобразованной форме конкурирует с прямым совпадением.
5. Проверяйте эвристику на реальных запросах. Ложное срабатывание приводит к выполнению лишнего запроса, а пропуск — к пустой выдаче.
6. Не применяйте преобразование к кодовым полям. Артикулы и идентификаторы содержат латиницу осознанно, и преобразование ломает точное совпадение.
7. Сочетайте с допуском опечаток. Неверная раскладка и опечатка встречаются вместе; о нечётком поиске — в статье про fuzzy-поиск.
Рекомендации:
- Фиксируйте таблицу соответствия в коде. Раскладка определяется стандартом, поэтому таблица проверяется по нему, а не составляется на глаз.
- Учитывайте раскладки других языков. Украинская, белорусская и другие раскладки содержат клавиши, отсутствующие в русской, поэтому таблица требует расширения.
- Измеряйте влияние на задержку. Два запроса вместо одного увеличивают время ответа, что критично для подсказок ввода.
- Логируйте срабатывания. Частота применения преобразования показывает, насколько проблема актуальна для конкретной аудитории.
- Проверяйте поведение на пустом результате. Преобразование выполняется при пустой либо короткой выдаче, а не безусловно.
Практический сценарий: пустая выдача при вводе в латинской раскладке
Контекст: пользователи сообщали, что поиск не находит товары при вводе названия латиницей.
- Диагностика. Запрос «yjnebr» возвращал пустой результат, а «ноутбук» — полный список. Нечёткий поиск с допуском двух правок не находил совпадений, поскольку расстояние между строками превышало порог.
- Причина. Соответствие между строками определяется раскладкой клавиатуры, а не редакторским расстоянием, поэтому механизмы нечёткого поиска не применимы.
- Действия. (а) Добавили таблицу соответствия клавиш и функцию преобразования; (б) включили нормализацию Unicode и приведение к нижнему регистру до преобразования; (в) реализовали выполнение двух вариантов запроса с понижением веса преобразованного; (г) применили преобразование только при пустом либо коротком результате, чтобы не увеличивать задержку обычных запросов; (д) собрали статистику срабатываний и проверили, что ложные преобразования не приводят к нерелевантной выдаче; (е) добавили набор запросов с неверной раскладкой в тесты.
- Результат. Запросы в неверной раскладке находят документы, а обычные запросы выполняются без дополнительной задержки.
Вывод: неверная раскладка не обрабатывается нечётким поиском, поскольку различие не сводится к нескольким правкам. Преобразование по таблице клавиш с выполнением обоих вариантов запроса решает задачу, а условное применение ограничивает влияние на задержку обычных запросов.
FAQ
Почему нечёткий поиск не находит запрос в неверной раскладке? Расстояние между строками превышает допустимый порог, поскольку различаются все символы, а не несколько.
Нужна ли нормализация Unicode перед преобразованием? Да, поскольку один видимый символ может быть представлен разными последовательностями кодов, и сравнение без нормализации даёт несовпадение.
Где выполнять преобразование — в индексе или в запросе? В запросе, если объём индекса критичен; в индексе, если важнее скорость ответа. Выбор определяется нагрузкой и размером данных.
Применяется ли преобразование к артикулам? Нет, кодовые поля содержат латиницу осознанно, и преобразование ломает точное совпадение.
Источники: Unicode Standard Annex 15 (Normalization Forms), Annex 35 (LDML), Python unicodedata — ссылки в шапке.