Как в таблице значений отобрать только строки с порядковым номером?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При обработке неструктурированных данных или результатов текстового импорта программисты часто сталкиваются с ситуацией, когда в колонку таблицы значений попадают разнородные текстовые строки. Среди них присутствуют как информационные комментарии, так и целевые позиции вида «1. Объект № 12», «2. Объект № 33», «10. Объект № 44». Наша задача — отфильтровать таблицу значений и оставить в ней только те строки, которые начинаются с порядкового номера.

Попытка перебрать условия вида Лев(Строка, 2) = "1." ИЛИ Лев(Строка, 2) = "2." неэффективна, так как заранее неизвестно общее количество элементов, а при переходе к двузначным номерам («10.») длина префикса меняется. Разберем наиболее надежные, производительные и универсальные способы решения этой задачи во встроенном языке 1С:Предприятия.

Способ 1. Поиск разделителя и посимвольная проверка префикса на число

Рассмотрим базовый алгоритмический подход, который гарантированно работает на любой версии платформы и операционной системы. Мы находим позицию первой точки в строке, выделяем подстроку, находящуюся слева от нее, и проверяем, состоит ли этот префикс исключительно из цифр.

Обратим внимание на важный нюанс: попытка использовать преобразование через XMLЗначение(Тип("Число"), ...) или блок Попытка — Исключение с функцией Число() является грубой ошибкой оптимизации. Если строка не является числом, XMLЗначение и Число() генерируют исключение времени выполнения, а постоянный перехват исключений на больших таблицах замедляет работу алгоритма в десятки раз. Поэтому реализуем чистую логическую проверку символов.

Посмотрим на пример универсальной функции проверки:


Функция ЭтоПорядковыйНомер(Знач ПроверяемаяСтрока)
	
	ПроверяемаяСтрока = СокрЛП(ПроверяемаяСтрока);
	ПозицияТочки = Найти(ПроверяемаяСтрока, ".");
	
	// Если точки нет или она стоит на первом месте (нет цифр перед ней)
	Если ПозицияТочки <= 1 Тогда
		Возврат Ложь;
	КонецЕсли;
	
	Префикс = СокрЛП(Лев(ПроверяемаяСтрока, ПозицияТочки - 1));
	
	Если ПустаяСтрока(Префикс) Тогда
		Возврат Ложь;
	КонецЕсли;
	
	// Проверяем каждый символ префикса по диапазону кодов цифр от '0' (48) до '9' (57)
	Для Позиция = 1 По СтрДлина(Префикс) Цикл
		Код = КодСимвола(Префикс, Позиция);
		Если Код < 48 Или Код > 57 Тогда
			Возврат Ложь;
		КонецЕсли;
	КонецЦикла;
	
	Возврат Истина;
	
КонецФункции

Использование функции КодСимвола() работает значительно быстрее, чем поиск подстроки через Найти("0123456789", Символ), так как исключает создание промежуточных строковых переменных в памяти на каждой итерации цикла.

Способ 2. Использование регулярных выражений

Проанализируем современный способ решения задачи. Начиная с версии платформы 8.3.23, в платформу 1С добавлена встроенная функция СтрПодобнаПоРегулярномуВыражению(). Это позволяет избавиться от написания вспомогательных процедур разбора текста.

При составлении шаблона критически важно использовать привязку к началу строки. Если написать шаблон вида ".*№\s*\d+.*", условие сработает на любую строку, где встречается знак номера с цифрой, даже если в начале строки нет порядкового номера. Чтобы проверить именно ведущий номер с точкой, используем регулярное выражение ^\s*\d+\..*, где:

  1. ^ — привязка к началу строки;
  2. \s* — возможное наличие начальных пробелов;
  3. \d+ — одна или более цифр порядкового номера;
  4. \. — экранированный символ точки, отделяющий номер от остального текста;
  5. .* — любой последующий текст.

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


Шаблон = "^\s*\d+\..*";
Для Каждого СтрТЗ Из ТаблицаДанных Цикл
	Если СтрПодобнаПоРегулярномуВыражению(СтрТЗ.ПорядковыйНомерОбъекта, Шаблон, Истина) Тогда
		// Строка содержит порядковый номер
	КонецЕсли;
КонецЦикла;

Важное ограничение: данный метод применим только на платформе 8.3.23 и выше. Использование внешних COM-объектов (например, VBScript.RegExp) для старых платформ не рекомендуется, так как такой код не сможет выполняться на серверах под управлением Linux и в веб-клиенте.

Способ 3. Фильтрация данных с помощью языка запросов

Если таблица значений уже содержит тысячи строк, обход коллекции в цикле на встроенном языке может выполняться медленнее, чем обработка силами СУБД. Мы можем передать таблицу значений в запрос и воспользоваться оператором ПОДОБНО.

Посмотрим на пример запроса, отбирающего нужные строки:


Запрос = Новый Запрос;
Запрос.Текст = 
	"ВЫБРАТЬ
	|	ИсходнаяТЗ.ПорядковыйНомерОбъекта КАК ПорядковыйНомерОбъекта
	|ПОМЕСТИТЬ ВТ_Данные
	|ИЗ
	|	&ТаблицаДанных КАК ИсходнаяТЗ
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ВТ_Данные.ПорядковыйНомерОбъекта КАК ПорядковыйНомерОбъекта
	|ИЗ
	|	ВТ_Данные КАК ВТ_Данные
	|ГДЕ
	|	ВТ_Данные.ПорядковыйНомерОбъекта ПОДОБНО ""[0-9]%.%""";

Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
ОтфильтрованнаяТаблица = Запрос.Выполнить().Выгрузить();

Шаблон [0-9]%.% проверяет, что строка начинается с любой цифры и далее по тексту содержит символ точки. Этот способ компактен и выполняется максимально быстро на стороне СУБД.

Правильная фильтрация строк внутри Таблицы Значений

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

Рассмотрим два корректных и эффективных способа получить результирующую таблицу значений.

Вариант 1. Сбор подходящих строк и метод Скопировать()

Наиболее лаконичный и быстрый способ: собираем ссылки на подходящие строки в массив, а затем создаем копию исходной таблицы с помощью встроенного метода платформы.


ПодходящиеСтроки = Новый Массив;

Для Каждого СтрТЗ Из ТаблицаДанных Цикл
	Если ЭтоПорядковыйНомер(СтрТЗ.ПорядковыйНомерОбъекта) Тогда
		ПодходящиеСтроки.Добавить(СтрТЗ);
	КонецЕсли;
КонецЦикла;

// Получаем готовую таблицу с сохранением исходной структуры колонок
РезультирующаяТЗ = ТаблицаДанных.Скопировать(ПодходящиеСтроки);

Вариант 2. Удаление лишних строк обратным циклом

Если необходимо модифицировать исходную таблицу значений по месту без выделения дополнительной памяти на создание копии, используем обход с конца к началу:


Для Индекс = ТаблицаДанных.Количество() - 1 По 0 Цикл
	СтрТЗ = ТаблицаДанных[Индекс];
	Если Не ЭтоПорядковыйНомер(СтрТЗ.ПорядковыйНомерОбъекта) Тогда
		ТаблицаДанных.Удалить(Индекс);
	КонецЕсли;
КонецЦикла;

При обратном цикле удаление текущего элемента сдвигает только элементы с большими индексами, которые мы уже проверили на предыдущих шагах, что полностью исключает пропуск строк.

← На главную