В процессе разработки часто возникают задачи, связанные с анализом текстовых данных. Одна из таких задач — определение количества уникальных кириллических символов в строке. Это может потребоваться для лингвистического анализа, валидации вводимых данных или создания различных фильтров. Рассмотрим подробнее, как эволюционировали подходы к решению этой задачи: от простых циклов до оптимизированных алгоритмов.
Самый очевидный способ — перебор строки посимвольно и сохранение каждого нового встреченного символа кириллицы в массив. Разберем по шагам логику этого подхода. Нам необходимо привести строку к одному регистру (чтобы "А" и "а" считались одним символом), а затем проверять каждый знак на принадлежность к русскому алфавиту.
Проанализируем первый предложенный вариант кода:
Функция ПосчитатьКоличествоРазличныхСимволовКириллицы(Знач СтрокаПарсинга) Экспорт
МассивНайденныхРазличныхСимволов = Новый Массив;
СтрокаПарсинга = СокрЛП(СтрокаПарсинга);
СтрокаПарсинга = нрег(СтрокаПарсинга);
ДлинаСтрокиПарсинга = СтрДлина(СтрокаПарсинга);
Если ДлинаСтрокиПарсинга > 0 Тогда
СимволыКириллицы = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";
Для а = 1 по ДлинаСтрокиПарсинга Цикл
ТекущийСимволСтроки = Сред(СтрокаПарсинга, а, 1);
Если МассивНайденныхРазличныхСимволов.Найти(ТекущийСимволСтроки) = Неопределено
И СтрНайти(СимволыКириллицы, ТекущийСимволСтроки) > 0 Тогда
МассивНайденныхРазличныхСимволов.Добавить(ТекущийСимволСтроки);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Возврат МассивНайденныхРазличныхСимволов.Количество();
КонецФункции
Несмотря на простоту, в этом коде есть два важных нюанса. Во-первых, важно не забыть про букву й и ё, которые иногда выпадают из подобных строк-эталонов. Во-вторых, метод Найти() для массива работает медленно на больших объемах данных, так как при каждой итерации программа вынуждена заново перебирать весь массив в поисках дубля.
Выясним причину низкой производительности предыдущего метода. Проблема заключается в линейном поиске в массиве. Чтобы ускорить процесс, проанализируем ситуацию и применим объект Соответствие. В 1С поиск по ключу в Соответствие реализован через хеш-таблицы, что значительно быстрее, чем поиск в массиве.
Рассмотрим улучшенный вариант функции:
Функция ПосчитатьУникальныеКириллицу(Знач СтрокаПарсинга) Экспорт
Соот = Новый Соответствие;
СимволыКириллицы = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";
СтрокаПарсинга = нрег(СтрокаПарсинга);
Для сч = 1 по СтрДлина(СтрокаПарсинга) Цикл
СимвКир = Сред(СтрокаПарсинга, сч, 1);
Если СтрНайти(СимволыКириллицы, СимвКир) > 0 Тогда
// Ключ уникален в соответствии по определению
Соот.Вставить(СимвКир, Истина);
КонецЕсли;
КонецЦикла;
Возврат Соот.Количество();
КонецФункции
Этот метод работает гораздо стабильнее. Даже если в строке будет миллион символов, проверка Соот.Вставить не потребует полного перебора коллекции, в отличие от метода Массив.Найти().
Посмотрим на пример гибридного решения. Иногда выгоднее сначала "очистить" строку от всего лишнего (латиницы, цифр, пробелов), а уже потом считать уникальные знаки в чистой кириллической последовательности. Это позволяет уменьшить количество итераций в основном цикле обработки уникальности.
Разберем по шагам алгоритм фильтрации:
СтрРазделить, чтобы разбить исходный текст на части, используя кириллицу как разделитель. В итоге мы получим все некириллические фрагменты.
СимволыКириллицы = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";
СимволыКириллицы = СимволыКириллицы + ВРЕГ(СимволыКириллицы);
// Убираем всё, что НЕ кириллица
МассивСтрокНеКириллицы = СтрРазделить(СтрокаПарсинга, СимволыКириллицы, Истина);
СтрокиНеКириллицы = СтрСоединить(МассивСтрокНеКириллицы);
// Теперь получаем только кириллицу
МассивСтрокКириллицы = СтрРазделить(СтрокаПарсинга, СтрокиНеКириллицы, Ложь);
ЧистаяКириллица = СтрСоединить(МассивСтрокКириллицы);
// Считаем уникальные через Соответствие
Соот = Новый Соответствие;
Для сч = 1 по СтрДлина(ЧистаяКириллица) Цикл
Симв = Сред(ЧистаяКириллица, сч, 1);
Соот.Вставить(нрег(Симв), Истина);
КонецЦикла;
Если нам требуется максимальная скорость, особенно при работе с большими текстами (например, анализ целой книги), стоит рассмотреть низкоуровневые способы или использование COM-объектов.
1. Использование регулярных выражений (RegExp)
Регулярные выражения позволяют мгновенно отсечь все символы, не входящие в диапазон [а-яёА-ЯЁ]. В среде Windows это можно сделать через объект VBScript.RegExp. Преимущество в том, что движок регулярных выражений написан на C++ и оптимизирован для поиска паттернов гораздо лучше, чем встроенный язык 1С.
2. Проверка кодов символов (Unicode)
Вместо использования функции СтрНайти внутри цикла, что заставляет платформу каждый раз искать подстроку в строке-шаблоне, мы можем сравнивать числовые коды символов. Коды кириллицы в Unicode расположены плотно. Основной блок букв (А-я) находится в диапазоне от 1040 до 1103. Исключением являются буквы Ё (код 1025) и ё (код 1105).
Проанализируем пример функции с проверкой кодов:
Функция ЭтоКириллица(Символ)
Код = КодСимвола(Символ);
// Проверка основного диапазона и буквы Ё
Возврат (Код >= 1040 И Код <= 1103) Или Код = 1025 Или Код = 1105;
КонецФункции
Сравнение чисел — это самая быстрая операция. Использование такой проверки в цикле вместе с Соответствие даст наилучший результат по скорости на платформе 1С.
В новых версиях платформы мы можем избежать использования функции Сред() в цикле. Функция Сред() создает новую строку в памяти при каждом обращении, что при больших объемах создает нагрузку на сборщик мусора. Вместо этого мы можем один раз преобразовать всю строку в массив символов, используя СтрРазделить(Строка, "").
Разберем комплексное, наиболее эффективное решение:
Функция ПодсчитатьУникальнуюКириллицуОптимизированно(Знач ВходнаяСтрока) Экспорт
Результат = Новый Соответствие;
// Преобразуем строку в массив символов сразу (для платформ 8.3.10+)
ВсеСимволы = СтрРазделить(нрег(ВходнаяСтрока), "");
Для Каждого ТекущийСимвол Из ВсеСимволы Цикл
Код = КодСимвола(ТекущийСимвол);
// Проверяем, что символ - кириллица
Если (Код >= 1072 И Код <= 1103) Или Код = 1105 Тогда
Результат.Вставить(ТекущийСимвол, Истина);
КонецЕсли;
КонецЦикла;
Возврат Результат.Количество();
КонецФункции
В данном примере мы использовали диапазон 1072–1103, так как предварительно привели строку к нижнему регистру функцией нрег(). Это избавляет от необходимости проверять коды заглавных букв.
Мы проанализировали различные методы решения задачи. Подведем итоги:
Соответствие и метод Сред().СтрРазделить(Строка, "") и проверку кодов КодСимвола().Выбор конкретного метода зависит от версии вашей платформы и объема обрабатываемых данных. Использование Соответствие вместо Массив является "золотым стандартом" при поиске уникальных значений в 1С.