Деление на слова

Механизмы сегментации текста в ECMAScript реализованы через объект Intl.Segmenter, предоставляющий стандартизированный способ разбиения строк на единицы языка: предложения, слова и символы. Режим работы с делением на слова используется для лингвистически корректного анализа текста, учитывающего правила конкретного языка и сложные случаи границ слов.

Intl.Segmenter и режим word

Сегментация на слова активируется через параметр granularity: "word". В этом режиме движок учитывает правила языковой разметки, а не простое разделение по пробелам.

const segmenter = new Intl.Segmenter("ru", { granularity: "word" });

const text = "Intl API позволяет корректно делить текст на слова.";
const segments = segmenter.segment(text);

for (const segment of segments) {
  console.log(segment.segment, segment.isWordLike);
}

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

Структура сегмента слова

Каждый элемент, возвращаемый segmenter.segment(), содержит:

  • segment — фрагмент исходной строки
  • index — позиция начала сегмента
  • input — исходная строка
  • isWordLike — логический признак, является ли сегмент словоподобным
{
  segment: "Intl",
  index: 0,
  input: "Intl API ...",
  isWordLike: true
}

Свойство isWordLike критично для фильтрации: в режиме "word" в поток сегментов включаются не только слова, но и знаки пунктуации, пробелы и символы-разделители.

Лингвистическая точность разбиения

Простое разбиение строки по регулярному выражению \s+ не учитывает специфику языков. Intl Segmenter опирается на Unicode Text Segmentation Algorithm (UAX #29), который учитывает:

  • сложные составные слова
  • сокращения
  • пунктуационные границы
  • особенности азиатских письменностей
  • отсутствие пробелов в некоторых языках

Например, в китайском тексте:

const segmenter = new Intl.Segmenter("zh", { granularity: "word" });

const text = "我喜欢编程";
const segments = [...segmenter.segment(text)].filter(s => s.isWordLike);

console.log(segments.map(s => s.segment));

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

Фильтрация словоподобных сегментов

Поскольку режим "word" возвращает не только слова, но и разделители, обычно применяется фильтрация по isWordLike.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });

const text = "Hello, world! Intl Segmenter.";

const words = [...segmenter.segment(text)]
  .filter(s => s.isWordLike)
  .map(s => s.segment);

console.log(words);

Результат:

["Hello", "world", "Intl", "Segmenter"]

Пунктуация исключается, так как она не относится к словоподобным сегментам.

Индексация и работа с позициями

Свойство index позволяет точно связывать сегменты с исходной строкой без дополнительного парсинга.

const segmenter = new Intl.Segmenter("en", { granularity: "word" });

const text = "Version 3.1.0 released";

for (const { segment, index, isWordLike } of segmenter.segment(text)) {
  if (isWordLike) {
    console.log(segment, "at", index);
  }
}

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

Поведение в разных локалях

Локаль влияет на правила определения границ слов. Например:

new Intl.Segmenter("en", { granularity: "word" });
new Intl.Segmenter("de", { granularity: "word" });
new Intl.Segmenter("fr", { granularity: "word" });

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

В английском:

don't → ["don", "'", "t"] (с последующей фильтрацией)

В некоторых локалях апостроф может трактоваться иначе, что влияет на итоговую сегментацию.

Сравнение с регулярными выражениями

Регулярные выражения часто применяются для разбиения текста:

const words = text.split(/\W+/);

Однако такой подход имеет ограничения:

  • не учитывает Unicode-графемы
  • ломается на языках без пробелов
  • не различает типы сегментов
  • не соответствует стандарту Unicode segmentation

Intl Segmenter устраняет эти ограничения, предоставляя единообразное поведение во всех средах выполнения.

Обработка сложных случаев

Составные слова и числа

const segmenter = new Intl.Segmenter("en", { granularity: "word" });

const text = "Node.js v18.16.0 supports ES2022";

Числовые и версионные конструкции разбиваются на логические части, сохраняя читаемость структуры.

Смешанные письменности

const text = "JavaScriptは強力です";

В таких случаях сегментация отделяет латиницу от японских символов, сохраняя корректные границы словоподобных элементов.

Практическая модель обхода сегментов

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

function extractWords(text, locale = "en") {
  const segmenter = new Intl.Segmenter(locale, { granularity: "word" });

  return [...segmenter.segment(text)]
    .filter(s => s.isWordLike)
    .map(s => ({
      word: s.segment,
      start: s.index,
      end: s.index + s.segment.length
    }));
}

Такая структура используется в:

  • поисковых индексах
  • подсветке синтаксиса
  • анализе текста
  • NLP-пайплайнах

Поведение isWordLike

Свойство isWordLike разделяет поток на две категории:

  • словесные элементы (слова, числа, символы языка)
  • разделители (пробелы, пунктуация, управляющие символы)
for (const s of segmenter.segment("Hello, world!")) {
  console.log(s.segment, s.isWordLike);
}

Результат:

Hello true
, false
  false
world true
! false

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

Ограничения модели сегментации

Механизм деления на слова не является лексическим анализатором и не выполняет:

  • морфологический разбор
  • определение частей речи
  • семантическую интерпретацию
  • нормализацию словоформ

Он строго ограничен задачей определения границ слов в соответствии с Unicode Text Segmentation.

Производительность сегментации

Intl Segmenter оптимизирован на уровне движка JavaScript и значительно эффективнее пользовательских регулярных выражений при больших объёмах текста. Повторное использование одного экземпляра сегментера уменьшает накладные расходы:

const segmenter = new Intl.Segmenter("en", { granularity: "word" });

function process(texts) {
  return texts.map(text =>
    [...segmenter.segment(text)].filter(s => s.isWordLike)
  );
}