Перейти к содержанию

Русский язык и технические термины

Поиск использует слова, выделенные токенизатором. Поэтому полезность индекса зависит не только от формулы ранжирования, но и от того, какие термины вообще сохранились. Если C# превращается в одну букву, а .NET теряет точку без согласованного правила, дальнейшая сортировка работает уже с изменённой задачей.

В этом уроке заменим токенизатор нашего core.js. Он научится сохранять несколько технических названий и объединять небольшой набор заранее проверенных русских словоформ. Это учебная политика конкретной библиотеки, а не универсальное решение русской морфологии.

Нормализация и смысл

Нормализация приводит разные записи к согласованному представлению. Уже введены нижний регистр, Unicode NFKC и замена ё на е. Они помогают сравнивать распространённые варианты, но не объясняют, что означает запрос.

Внутренний термин может отличаться от показываемого заголовка. Например, C# удобно представить как csharp, а .NET — как dotnet. Посетитель при этом продолжает видеть исходный текст. Менять заголовок или URL ради внутреннего словаря не требуется.

Нужно одинаково обрабатывать документ и запрос. Если C# в документе заменён на csharp, а в запросе оставлен как c, совпадение будет потеряно. Поэтому обе стороны используют одну функцию tokenize.

Другая опасность — слишком широкое объединение. Слова, которые похожи по написанию, не всегда обозначают одно понятие. Техническое сокращение может совпадать с обычным словом. Политика должна быть объяснима на примерах, а её изменения требуют повторной оценки запросов.

Технические названия

Регулярное выражение сначала ищет известные составные названия, затем обычные последовательности букв и цифр. Порядок вариантов важен: если распознать C как отдельное слово раньше C#, решётка останется за пределами термина.

В public/core.js замените tokenize и добавьте две константы перед ней. normalize, построение индекса и функция запроса сохраняются.

const TECH = new Map([
  ["c#", "csharp"],
  ["c++", "cpp"],
  [".net", "dotnet"],
  ["node.js", "nodejs"]
]);

const FORMS = new Map([
  ["таблицы", "таблица"],
  ["таблиц", "таблица"],
  ["таблицу", "таблица"],
  ["таблицами", "таблица"],
  ["соединить", "соединение"],
  ["запросы", "запрос"],
  ["запросов", "запрос"],
  ["запроса", "запрос"]
]);

export function tokenize(value) {
  const raw = normalize(value).match(/c\+\+|c#|\.net|node\.js|[\p{L}\p{N}_]+/gu) || [];
  return [...new Set(raw.map(term => {
    const technical = TECH.get(term) || term;
    return FORMS.get(technical) || technical;
  }))];
}

Набор TECH описывает согласованные внутренние имена. Набор FORMS связывает только перечисленные варианты. Например, «таблицы» и «таблиц» станут таблица, но программа не умеет автоматически разбирать все падежи каждого русского слова.

Обратите внимание на слово «соединить». Его преобразование связано с известной задачей нашего SQL-урока. В другой библиотеке может понадобиться отличать физическое соединение, сетевое подключение и соединение таблиц. Само совпадение формы не гарантирует одинаковый смысл.

Ожидаемые преобразования

Для небольшого ручного разбора можно вызвать токенизатор с несколькими строками. Эти выражения используют импортированную функцию из core.js:

console.log(tokenize("C# и .NET"));
console.log(tokenize("Соединить таблицы"));
console.log(tokenize("Node.js"));

По показанным правилам ожидаются такие термины:

["csharp", "и", "dotnet"]
["соединение", "таблица"]
["nodejs"]

Слово «и» осталось. Мы пока не вводим глобальный список стоп-слов: короткий запрос из обычного слова может иметь самостоятельный смысл. В дальнейшем можно ограниченно удалять служебные слова из длинных естественных запросов, но это отдельное изменение поведения.

После замены токенизатора индекс строится заново. Запрос C# должен использовать термин csharp; запрос «соединить таблицы» теперь сопоставим с текстом «соединение таблиц». Прежняя формула весов применяется уже к новым терминам.

Нормализация не меняет сами документы. Поэтому карточка продолжает показывать «Типы данных C#», а ссылка остаётся на постоянном адресе. Такой порядок позволяет улучшать обработку запроса без редактирования содержимого ради поискового словаря.

Почему нельзя просто обрезать окончания

Можно попытаться удалить несколько последних букв у каждого русского слова. Это выглядит как быстрый способ объединить формы, но правило создаёт множество ложных совпадений и повреждает короткие термины. В технической библиотеке рядом встречаются русские слова, английские идентификаторы и названия продуктов.

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

Синонимы отличаются от словоформ. «JS» и «JavaScript» — разные написания названия; «таблицы» и «таблиц» — формы одного слова. В следующем уроке будем расширять запрос альтернативами, а не добавлять любые связи в один необозримый список замен.

Опечатки тоже относятся к другой задаче. ngnix не является грамматической формой Nginx. Исправление потребует сравнения похожих терминов. Если смешать все механизмы сразу, будет трудно понять, почему найден неожиданный документ.

Граница регулярного выражения

Наш токенизатор выделяет известные составные названия даже внутри более длинной строки. Для полного языка идентификаторов могут понадобиться правила границ: нельзя автоматически объявлять любое вхождение последовательности названием продукта. Такой случай полезно добавить в контрольный набор.

Номера версий тоже пока разбиваются на отдельные части. Если запросы 3.11 и 3.1 имеют разное значение для библиотеки, потребуется отдельное представление версии. Нельзя обещать сохранение всех технических обозначений по четырём специальным вариантам.

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

Сверка с задачами читателя

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

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

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

После изменения словаря нужно заново построить индекс. Уже сохранённые postings содержат прежние внутренние термины; подключение нового токенизатора только к запросу не исправит их автоматически. Сначала сравните, какие токены получает документ, затем примените ту же функцию к строке посетителя. Совместимость обеих сторон важнее количества правил в словаре.

Теперь индекс учитывает несколько важных особенностей технического русского текста. Следующий урок добавит альтернативы и небольшое исправление опечаток, сохраняя различие между точным совпадением и расширенным поиском.