При обработке неструктурированных данных или результатов текстового импорта программисты часто сталкиваются с ситуацией, когда в колонку таблицы значений попадают разнородные текстовые строки. Среди них присутствуют как информационные комментарии, так и целевые позиции вида «1. Объект № 12», «2. Объект № 33», «10. Объект № 44». Наша задача — отфильтровать таблицу значений и оставить в ней только те строки, которые начинаются с порядкового номера.
Попытка перебрать условия вида Лев(Строка, 2) = "1." ИЛИ Лев(Строка, 2) = "2." неэффективна, так как заранее неизвестно общее количество элементов, а при переходе к двузначным номерам («10.») длина префикса меняется. Разберем наиболее надежные, производительные и универсальные способы решения этой задачи во встроенном языке 1С:Предприятия.
Рассмотрим базовый алгоритмический подход, который гарантированно работает на любой версии платформы и операционной системы. Мы находим позицию первой точки в строке, выделяем подстроку, находящуюся слева от нее, и проверяем, состоит ли этот префикс исключительно из цифр.
Обратим внимание на важный нюанс: попытка использовать преобразование через XMLЗначение(Тип("Число"), ...) или блок Попытка — Исключение с функцией Число() является грубой ошибкой оптимизации. Если строка не является числом, XMLЗначение и Число() генерируют исключение времени выполнения, а постоянный перехват исключений на больших таблицах замедляет работу алгоритма в десятки раз. Поэтому реализуем чистую логическую проверку символов.
Посмотрим на пример универсальной функции проверки:
Функция ЭтоПорядковыйНомер(Знач ПроверяемаяСтрока)
ПроверяемаяСтрока = СокрЛП(ПроверяемаяСтрока);
ПозицияТочки = Найти(ПроверяемаяСтрока, ".");
// Если точки нет или она стоит на первом месте (нет цифр перед ней)
Если ПозицияТочки <= 1 Тогда
Возврат Ложь;
КонецЕсли;
Префикс = СокрЛП(Лев(ПроверяемаяСтрока, ПозицияТочки - 1));
Если ПустаяСтрока(Префикс) Тогда
Возврат Ложь;
КонецЕсли;
// Проверяем каждый символ префикса по диапазону кодов цифр от '0' (48) до '9' (57)
Для Позиция = 1 По СтрДлина(Префикс) Цикл
Код = КодСимвола(Префикс, Позиция);
Если Код < 48 Или Код > 57 Тогда
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Возврат Истина;
КонецФункции
Использование функции КодСимвола() работает значительно быстрее, чем поиск подстроки через Найти("0123456789", Символ), так как исключает создание промежуточных строковых переменных в памяти на каждой итерации цикла.
Проанализируем современный способ решения задачи. Начиная с версии платформы 8.3.23, в платформу 1С добавлена встроенная функция СтрПодобнаПоРегулярномуВыражению(). Это позволяет избавиться от написания вспомогательных процедур разбора текста.
При составлении шаблона критически важно использовать привязку к началу строки. Если написать шаблон вида ".*№\s*\d+.*", условие сработает на любую строку, где встречается знак номера с цифрой, даже если в начале строки нет порядкового номера. Чтобы проверить именно ведущий номер с точкой, используем регулярное выражение ^\s*\d+\..*, где:
Разберем пример проверки строки с использованием встроенных регулярных выражений:
Шаблон = "^\s*\d+\..*";
Для Каждого СтрТЗ Из ТаблицаДанных Цикл
Если СтрПодобнаПоРегулярномуВыражению(СтрТЗ.ПорядковыйНомерОбъекта, Шаблон, Истина) Тогда
// Строка содержит порядковый номер
КонецЕсли;
КонецЦикла;
Важное ограничение: данный метод применим только на платформе 8.3.23 и выше. Использование внешних COM-объектов (например, VBScript.RegExp) для старых платформ не рекомендуется, так как такой код не сможет выполняться на серверах под управлением Linux и в веб-клиенте.
Если таблица значений уже содержит тысячи строк, обход коллекции в цикле на встроенном языке может выполняться медленнее, чем обработка силами СУБД. Мы можем передать таблицу значений в запрос и воспользоваться оператором ПОДОБНО.
Посмотрим на пример запроса, отбирающего нужные строки:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходнаяТЗ.ПорядковыйНомерОбъекта КАК ПорядковыйНомерОбъекта
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТаблицаДанных КАК ИсходнаяТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Данные.ПорядковыйНомерОбъекта КАК ПорядковыйНомерОбъекта
|ИЗ
| ВТ_Данные КАК ВТ_Данные
|ГДЕ
| ВТ_Данные.ПорядковыйНомерОбъекта ПОДОБНО ""[0-9]%.%""";
Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
ОтфильтрованнаяТаблица = Запрос.Выполнить().Выгрузить();
Шаблон [0-9]%.% проверяет, что строка начинается с любой цифры и далее по тексту содержит символ точки. Этот способ компактен и выполняется максимально быстро на стороне СУБД.
Выбрав подходящий метод проверки строки, обратим особое внимание на частую ошибку начинающих разработчиков — удаление строк из таблицы значений внутри прямого цикла Для Каждого. При вызове метода ТЗ.Удалить(Строка) индексы коллекции немедленно смещаются, из-за чего итератор пропускает строку, идущую следом за удаленной.
Рассмотрим два корректных и эффективных способа получить результирующую таблицу значений.
Вариант 1. Сбор подходящих строк и метод Скопировать()
Наиболее лаконичный и быстрый способ: собираем ссылки на подходящие строки в массив, а затем создаем копию исходной таблицы с помощью встроенного метода платформы.
ПодходящиеСтроки = Новый Массив;
Для Каждого СтрТЗ Из ТаблицаДанных Цикл
Если ЭтоПорядковыйНомер(СтрТЗ.ПорядковыйНомерОбъекта) Тогда
ПодходящиеСтроки.Добавить(СтрТЗ);
КонецЕсли;
КонецЦикла;
// Получаем готовую таблицу с сохранением исходной структуры колонок
РезультирующаяТЗ = ТаблицаДанных.Скопировать(ПодходящиеСтроки);
Вариант 2. Удаление лишних строк обратным циклом
Если необходимо модифицировать исходную таблицу значений по месту без выделения дополнительной памяти на создание копии, используем обход с конца к началу:
Для Индекс = ТаблицаДанных.Количество() - 1 По 0 Цикл
СтрТЗ = ТаблицаДанных[Индекс];
Если Не ЭтоПорядковыйНомер(СтрТЗ.ПорядковыйНомерОбъекта) Тогда
ТаблицаДанных.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
При обратном цикле удаление текущего элемента сдвигает только элементы с большими индексами, которые мы уже проверили на предыдущих шагах, что полностью исключает пропуск строк.