Как обойти все уровни дерева значений и удалить строки по условию в 1С

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Торговля и дистрибуция
← На главную

Работа с Деревом значений в 1С отличается от работы с обычной таблицей из-за иерархической структуры. Основная сложность заключается в том, что количество уровней вложенности заранее может быть неизвестно. В этой статье мы разберем, как эффективно перебирать все уровни дерева и как правильно удалять из него данные, не нарушая целостность коллекции.

Рекурсивный обход всех уровней дерева

Для того чтобы обработать каждую строку дерева, независимо от того, на каком уровне она находится, чаще всего используется рекурсия. Это метод, при котором процедура вызывает саму себя для обработки подчиненных строк. Рассмотрим классический пример реализации такого обхода:


Процедура ОбходДереваРекурсивно(КоллекцияСтрок)
    Для Каждого ТекущаяСтрока Из КоллекцияСтрок Цикл
        // Выполняем полезные действия со строкой
        Сообщить(ТекущаяСтрока.Номенклатура);
        
        // Если у строки есть подчиненные строки, вызываем эту же процедуру
        Если ТекущаяСтрока.Строки.Количество() > 0 Тогда
            ОбходДереваРекурсивно(ТекущаяСтрока.Строки);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Чтобы запустить этот процесс, в основной части кода нужно передать корневую коллекцию строк вашего дерева:

ОбходДереваРекурсивно(ДеревоОстатков.Строки);

Важный момент: если дерево очень глубокое (сотни уровней), рекурсия может привести к переполнению стека. В таких редких случаях вместо рекурсии используют массив (стек), в который помещают строки для обработки в цикле Пока.

Как найти строки без рекурсии

Если ваша задача заключается только в поиске строк по определенному значению колонки, в 1С предусмотрен штатный метод НайтиСтроки. В отличие от аналогичного метода в таблице значений, у дерева значений этот метод ищет данные сразу на всех уровнях иерархии.


ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Удалить", Истина);

// Метод вернет массив строк, найденных на любом уровне вложенности
МассивСтрокДляУдаления = Дерево.Строки.НайтиСтроки(ПараметрыОтбора, Истина);

Использование этого метода избавляет от написания сложных циклов, если вам нужно просто получить список объектов по фильтру.

Проблема удаления строк из дерева значений

Типичная ошибка при удалении строк из дерева — попытка удалить строку напрямую через корневую коллекцию Дерево.Строки.Удалить(Строка). Этот метод сработает только для строк первого уровня. Если строка находится глубже, платформа выдаст ошибку или действие не будет выполнено, так как строка принадлежит коллекции своего родителя, а не корню дерева.

Правильное удаление через свойство Родитель

У каждой строки дерева значений есть свойство Родитель. Оно указывает на вышестоящую строку. Если свойство имеет значение Неопределено, значит, перед нами строка верхнего уровня. Мы можем использовать это для универсального удаления:


Процедура УдалитьСтрокуДерева(ВыбраннаяСтрока, Дерево)
    Родитель = ВыбраннаяСтрока.Родитель;
    
    Если Родитель = Неопределено Тогда
        // Удаляем из корня дерева
        Дерево.Строки.Удалить(ВыбраннаяСтрока);
    Иначе
        // Удаляем из коллекции родителя
        Родитель.Строки.Удалить(ВыбраннаяСтрока);
    КонецЕсли;
КонецПроцедуры

Массовое удаление строк из массива

Если вы предварительно собрали массив строк на удаление (например, через НайтиСтроки или рекурсивный обход), нужно учитывать два правила:

  1. Проверка существования: Если вы удаляете родительскую строку, все её дочерние строки удаляются автоматически. Если дочерняя строка также была в вашем массиве на удаление, попытка обратиться к ней вызовет ошибку.
  2. Обратный порядок: При обходе коллекции индексы смещаются. Если вы удаляете строки в цикле по индексу, всегда идите от большего к меньшему.

Рассмотрим пример безопасного удаления из массива:


Для Каждого СтрНаУдаление Из МассивНаУдаление Цикл
    // Проверяем, не была ли строка удалена ранее вместе с родителем
    Если СтрНаУдаление.Владелец() = Неопределено Тогда 
        Продолжить;
    КонецЕсли;
    
    // Определяем коллекцию, из которой нужно удалить
    Коллекция = ?(СтрНаУдаление.Родитель = Неопределено, Дерево.Строки, СтрНаУдаление.Родитель.Строки);
    Коллекция.Удалить(СтрНаУдаление);
КонецЦикла;

Альтернативный подход: Очистка и копирование

Иногда, если дерево содержит тысячи строк и нужно удалить большую их часть, эффективнее не удалять строки по одной, а создать новое дерево. Проанализируем этот сценарий: вы можете использовать метод Скопировать() для создания пустой структуры дерева, а затем рекурсивно переносить в него только те строки, которые соответствуют вашим условиям. Это может работать быстрее, так как операция удаления в сложных иерархиях ресурсозатратна.

Также помните о методе Очистить(). Его можно применять к коллекции Строки любого узла. Это мгновенно удаляет всю ветку подчиненных элементов, что гораздо эффективнее, чем перебор и удаление каждой дочерней строки по отдельности.

← На главную