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