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