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