При разработке обработок и документов часто возникает задача очистить табличную часть или коллекцию строк от пустых, нулевых или неактуальных позиций. Начинающие разработчики нередко сталкиваются с ситуацией, когда процедура удаления срабатывает не полностью и часть строк, удовлетворяющих условию, остается на месте. Разберем причину такого поведения платформы 1С и рассмотрим надежные способы решения этой задачи.
Посмотрим на типичный ошибочный код, в котором проявляется проблема:
Процедура УдалитьСтрокиУслугиНаСервере()
Для Каждого стр Из Объект.Услуги Цикл
Если стр.Количество = 0 Тогда
Объект.Услуги.Удалить(стр);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Выясним причину ошибки. При выполнении прямого обхода коллекции с помощью цикла Для Каждого платформа последовательно перемещает внутренний указатель (итератор) от начального элемента к конечному по их индексам.
Когда мы вызываем метод Удалить(), происходят следующие изменения:
Объект.Услуги.Если в табличной части идут две или более подходящие под условие строки подряд, первая из них будет удалена, а вторая перескочит на ее место и цикл ее проигнорирует. Чтобы удалить все строки корректно, необходимо использовать один из правильных шаблонов разработки.
Проанализируем самый распространенный и безопасный метод: сначала мы обходим коллекцию и собираем ссылки на удаляемые строки во вспомогательный массив, а затем отдельным циклом удаляем элементы из исходной коллекции.
Процедура УдалитьСтрокиУслугиНаСервере()
МассивДляУдаления = Новый Массив;
Для Каждого стр Из Объект.Услуги Цикл
Если стр.Количество = 0 Тогда
МассивДляУдаления.Добавить(стр);
КонецЕсли;
КонецЦикла;
Для Каждого СтрокаМассива Из МассивДляУдаления Цикл
Объект.Услуги.Удалить(СтрокаМассива);
КонецЦикла;
КонецПроцедуры
Преимущества: код легко читается, коллекция не модифицируется во время первого обхода, а цикл по массиву не зависит от смещения индексов в табличной части.
Если критерий отбора представляет собой точное равенство определенного реквизита (например, Количество = 0), мы можем использовать встроенный метод коллекции НайтиСтроки(). Этот метод возвращает массив строк, полностью соответствующих структуре отбора.
Процедура УдалитьСтрокиУслугиНаСервере()
ПараметрыОтбора = Новый Структура("Количество", 0);
СтрокиКУдалению = Объект.Услуги.НайтиСтроки(ПараметрыОтбора);
Для Каждого СтрокаКУдалению Из СтрокиКУдалению Цикл
Объект.Услуги.Удалить(СтрокаКУдалению);
КонецЦикла;
КонецПроцедуры
Важное ограничение: метод НайтиСтроки() выполняет поиск исключительно по строгому равенству значений. Если условие более сложное (например, Количество < 0 или проверка нескольких условий через «ИЛИ»), данный способ не подойдет и потребуется использовать сбор в массив с ручной проверкой или обход по индексам.
Разберем классический алгоритмический прием — проход по коллекции от последнего элемента к первому. При удалении элемента с конца коллекции индексы всех элементов, расположенных перед ним, остаются неизменными, поэтому пропусков строк не происходит.
Поскольку в языке 1С цикл Для ... По ... не поддерживает шаг назад (отрицательный шаг), обратный обход реализуется с помощью цикла Пока или вычисления обратного индекса.
Вариант через цикл Пока:
Процедура УдалитьСтрокиУслугиНаСервере()
Индекс = Объект.Услуги.Количество() - 1;
Пока Индекс >= 0 Цикл
Если Объект.Услуги[Индекс].Количество = 0 Тогда
Объект.Услуги.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
КонецПроцедуры
Вариант через цикл Для:
Процедура УдалитьСтрокиУслугиНаСервере()
ВсегоСтрок = Объект.Услуги.Количество();
Для Сч = 1 По ВсегоСтрок Цикл
Индекс = ВсегоСтрок - Сч;
Если Объект.Услуги[Индекс].Количество = 0 Тогда
Объект.Услуги.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Удаление по индексу (Объект.Услуги.Удалить(Индекс)) выполняется быстрее, чем удаление по ссылке на строку, так как платформе не требуется искать объект в памяти коллекции.
Если необходимо сохранить прямой порядок обхода коллекции без выделения дополнительной памяти под массив, можно управлять счетчиком индекса вручную. Увеличивать индекс следует только в том случае, если строка не была удалена:
Процедура УдалитьСтрокиУслугиНаСервере()
Индекс = 0;
Пока Индекс < Объект.Услуги.Количество() Цикл
Если Объект.Услуги[Индекс].Количество = 0 Тогда
Объект.Услуги.Удалить(Индекс);
Иначе
Индекс = Индекс + 1;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
При обработке десятков тысяч строк построчное удаление из реквизита формы типа ДанныеФормыКоллекция может замедлять работу из-за частых синхронизаций интерфейсных данных. В таких случаях рекомендуется:
РеквизитФормыВЗначение().ТаблицаЗначений, отфильтровать нужные строки запросом или методом Скопировать().ЗначениеВРеквизитФормы() или метода Загрузить().