При разработке на платформе 1С:Предприятие программисты регулярно сталкиваются с необходимостью фильтрации таблиц значений или табличных частей документов. Самая частая подзадача здесь — обойти коллекцию в цикле и удалить строки, удовлетворяющие определенному условию. Однако обычный обход коллекции «в лоб» через стандартный цикл Для Каждого приводит к логической ошибке: при удалении элемента текущие индексы оставшихся строк смещаются, счетчик цикла сбивается, и платформа пропускает следующую за удаленной строку.
Для обхода этой проблемы разработчики часто используют двухпроходной алгоритм: сначала собирают удаляемые строки во вспомогательный массив, а затем отдельным циклом удаляют их из исходной таблицы. Рассмотрим, как избавиться от двойного обхода, решить задачу за один проход и существенно оптимизировать этот процесс с точки зрения производительности системы.
Самый простой и надежный способ удалить строки за один проход без сбоя индексов — обходить таблицу в обратном порядке (с конца к началу). Когда мы удаляем строку с текущим индексом, все последующие элементы действительно смещаются назад, однако нас это больше не волнует: эти элементы мы уже обработали на предыдущих шагах. Элементы, которые нам еще предстоит обработать (с меньшими индексами), остаются на своих исходных позициях.
Поскольку стандартная конструкция Для в языке 1С не поддерживает отрицательный шаг обхода (нет аналога конструкции Step -1), мы построим реверсивный цикл с помощью конструкции Пока. Разберем по шагам, как это реализовать:
Индекс = ТЗ.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТЗ = ТЗ[Индекс];
Если СтрокаТЗ.Статус = "КУдалению" Тогда
ТЗ.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Этот способ является универсальным стандартом разработки. Мы начинаем обход с максимального индекса ТЗ.Количество() - 1 и двигаемся к нулю, декрементируя счетчик на каждом шаге.
Если по каким-то причинам нам необходимо обходить таблицу строго с начала (например, если логика программы завязана на последовательный анализ строк сверху вниз), мы можем применить прямой обход в один проход. Для этого мы будем использовать цикл Пока, но управлять увеличением индекса будем вручную и только тогда, когда строка не была удалена.
Проанализируем этот алгоритм на примере кода:
Индекс = 0;
Пока Индекс < ТЗ.Количество() Цикл
СтрокаТЗ = ТЗ[Индекс];
Если СтрокаТЗ.Статус = "КУдалению" Тогда
ТЗ.Удалить(Индекс); // Строка удалена, следующая автоматически сместилась на текущий Индекс. Счетчик не увеличиваем!
Иначе
Индекс = Индекс + 1; // Переходим к следующему элементу, только если текущий остался на месте
КонецЕсли;
КонецЦикла;
Обратим внимание на критически важный момент: когда условие выполняется, метод Удалить(Индекс) стирает текущую строку, и ее место тут же занимает следующая за ней строка. Чтобы не пропустить ее анализ, мы оставляем переменную Индекс без изменений, и на следующей итерации цикла анализируем уже новую строку под тем же самым индексом.
Существует изящный способ запустить быстрый платформенный цикл Для в обратную сторону без использования конструкции Пока. Мы можем математически инвертировать счетчик цикла. Рассмотрим этот необычный прием:
Для Инд = -ТЗ.Количество() + 1 По 0 Цикл
СтрокаТЗ = ТЗ[-Инд];
Если СтрокаТЗ.Статус = "КУдалению" Тогда
ТЗ.Удалить(-Инд);
КонецЕсли;
КонецЦикла;
Выясним, как работает эта математика. Если в нашей таблице 10 строк, то переменная Инд будет последовательно принимать значения от -9 до 0 с шагом +1. Внутри тела цикла мы обращаемся к строке по индексу -Инд. На первом шаге это будет -(-9) = 9, на втором -(-8) = 8, и так далее до 0. Это позволяет использовать лаконичный цикл Для, сохраняя производительность и избавляясь от ручного управления счетчиком.
Если перед нами стоит задача обработать не просто независимую таблицу значений в оперативной памяти, а реальную табличную часть (ТЧ) документа на форме, построчное удаление через Удалить() напрямую из коллекции ТЧ будет крайне неэффективным. Причина кроется во внутренних механизмах платформы 1С:
Чтобы избежать падения производительности, мы применим стандартный паттерн разработки высоконагруженных систем: выгрузим данные в буферную таблицу значений, быстро обработаем ее в памяти и загрузим обратно одной транзакцией.
Посмотрим на пример практической реализации:
// Шаг 1. Быстро выгружаем данные ТЧ в локальную Таблицу Значений
БуфернаяТЗ = ЭтотОбъект.Товары.Выгрузить();
// Шаг 2. Выполняем быстрое удаление в памяти (например, обратным обходом)
Индекс = БуфернаяТЗ.Количество() - 1;
Пока Индекс >= 0 Цикл
Если БуфернаяТЗ[Индекс].Количество = 0 Тогда
БуфернаяТЗ.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
// Шаг 3. Загружаем отфильтрованную таблицу обратно в объект
ЭтотОбъект.Товары.Загрузить(БуфернаяТЗ);
При использовании метода Загрузить() вся коллекция перезаписывается мгновенно. Пересчет итоговых полей документа и обновление экранных форм произойдет ровно один раз в самом конце, что увеличивает скорость работы алгоритма в десятки раз на объемах данных более 100 строк.
Если условие фильтрации достаточно простое, а удалению подлежит значительная часть таблицы (более 30-50% строк), самым быстрым решением будет не удаление ненужных строк, а перенос нужных строк в новую таблицу значений с помощью платформенных методов, написанных на низкоуровневом языке C++.
Рассмотрим вариант с использованием метода Скопировать(). Если критерием фильтрации является простое равенство реквизита какому-то значению, мы можем получить итоговую таблицу без циклов вообще:
// Отбираем только нужные строки, которые должны ОСТАТЬСЯ в таблице
Отбор = Новый Структура("Статус", "Активен");
НоваяТЗ = ТЗ.Скопировать(Отбор);
Если же условия отбора сложные, мы можем использовать метод копирования по массиву строк. В цикле мы не будем удалять элементы, а просто соберем ссылки на качественные строки в массив, а затем скопируем их одной системной командой:
МассивОставляемыхСтрок = Новый Массив;
Для Каждого СтрокаТЗ Из ТЗ Цикл
// Сложное условие, по которому строка должна быть сохранена
Если СтрокаТЗ.Количество > 0 И СтрокаТЗ.Цена > 100 Тогда
МассивОставляемыхСтрок.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
// Платформа мгновенно создает новую ТЗ, содержащую только нужные строки
НоваяТЗ = ТЗ.Скопировать(МассивОставляемыхСтрок);
Этот подход минимизирует количество операций по изменению структуры данных в памяти компьютера, предотвращая постоянную переаллокацию адресов элементов и существенно ускоряя выполнение серверного кода.