Примеры рекурсивного обхода дерева значений

Примеры рекурсивного обхода дерева значений на языке программирования 1С:Предприятие. Примеры позволяют быстро разобраться в вопросе и использовать код в своих разработках

Рекурсивный обход для подсчета суммы всех элементов

// Рекурсивный подсчет суммы числовых значений во всех узлах дерева
Процедура РекурсивныйПодсчетСуммы()

    // Создаем тестовое дерево
    Дерево = СоздатьТестовоеДерево();
    
    // Вычисляем общую сумму рекурсивно
    ОбщаяСумма = ПодсчитатьСуммуРекурсивно(Дерево.Строки);
    Сообщить("Общая сумма всех элементов: " + ОбщаяСумма);
    
    // Вычисляем количество узлов
    КоличествоУзлов = ПодсчитатьКоличествоУзлов(Дерево.Строки);
    Сообщить("Общее количество узлов: " + КоличествоУзлов);
    
КонецПроцедуры

Функция СоздатьТестовоеДерево()
    Дерево = Новый ДеревоЗначений;
    Дерево.Колонки.Добавить("Наименование");
    Дерево.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
    
    Корень1 = Дерево.Строки.Добавить();
    Корень1.Наименование = "Категория 1";
    Корень1.Сумма = 1000;
    
    Дочерний1 = Корень1.Строки.Добавить();
    Дочерний1.Наименование = "Товар 1.1";
    Дочерний1.Сумма = 500;
    
    Дочерний2 = Корень1.Строки.Добавить();
    Дочерний2.Наименование = "Товар 1.2";
    Дочерний2.Сумма = 300;
    
    Внук1 = Дочерний2.Строки.Добавить();
    Внук1.Наименование = "Подтовар 1.2.1";
    Внук1.Сумма = 200;
    
    Корень2 = Дерево.Строки.Добавить();
    Корень2.Наименование = "Категория 2";
    Корень2.Сумма = 2000;
    
    Дочерний3 = Корень2.Строки.Добавить();
    Дочерний3.Наименование = "Товар 2.1";
    Дочерний3.Сумма = 800;
    
    Возврат Дерево;
КонецФункции

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

Функция ПодсчитатьКоличествоУзлов(СтрокиДерева)
    Количество = 0;
    
    Для Каждого Строка Из СтрокиДерева Цикл
        Количество = Количество + 1;
        
        Если Строка.Строки.Количество() > 0 Тогда
            Количество = Количество + ПодсчитатьКоличествоУзлов(Строка.Строки);
        КонецЕсли;
    КонецЦикла;
    
    Возврат Количество;
КонецФункции

Рекурсивный обход с накоплением информации о пути

// Обход дерева с формированием полного пути к каждому узлу
Процедура ОбходСФормированиемПути()

    Дерево = СоздатьМногоуровневоеДерево();
    
    // Формируем пути для всех узлов
    Пути = Новый Соответствие;
    СформироватьПутиРекурсивно(Дерево.Строки, "", Пути);
    
    // Выводим все пути
    Сообщить("Пути к узлам дерева:");
    Для Каждого Пара Из Пути Цикл
        Сообщить(Пара.Ключ + " -> " + Пара.Значение);
    КонецЦикла;
    
    // Находим узел по пути
    НайденныйУзел = НайтиУзелПоПути(Дерево, "Категория 1/Товар 1.1");
    Если НайденныйУзел <> Неопределено Тогда
        Сообщить("Найден узел: " + НайденныйУзел.Наименование);
    КонецЕсли;
    
КонецПроцедуры

Функция СоздатьМногоуровневоеДерево()
    Дерево = Новый ДеревоЗначений;
    Дерево.Колонки.Добавить("Наименование");
    
    Корень1 = Дерево.Строки.Добавить();
    Корень1.Наименование = "Категория 1";
    
    Уровень2 = Корень1.Строки.Добавить();
    Уровень2.Наименование = "Товар 1.1";
    
    Уровень3 = Уровень2.Строки.Добавить();
    Уровень3.Наименование = "Подтовар 1.1.1";
    
    Корень2 = Дерево.Строки.Добавить();
    Корень2.Наименование = "Категория 2";
    
    Возврат Дерево;
КонецФункции

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

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

Рекурсивный поиск узлов по условию

// Поиск всех узлов, удовлетворяющих заданному условию
Процедура РекурсивныйПоискПоУсловию()

    Дерево = СоздатьДеревоСотрудников();
    
    // Поиск сотрудников с зарплатой более 60000
    Результат1 = НайтиПоУсловию(Дерево.Строки, "Зарплата > 60000");
    Сообщить("Сотрудники с зарплатой > 60000:");
    Для Каждого Сотрудник Из Результат1 Цикл
        Сообщить("  " + Сотрудник.Наименование + " - " + Сотрудник.Зарплата);
    КонецЦикла;
    
    // Поиск сотрудников с возрастом менее 30 лет
    Результат2 = НайтиПоУсловию(Дерево.Строки, "Возраст < 30");
    Сообщить("Сотрудники младше 30 лет:");
    Для Каждого Сотрудник Из Результат2 Цикл
        Сообщить("  " + Сотрудник.Наименование + " - " + Сотрудник.Возраст + " лет");
    КонецЦикла;
    
    // Поиск с составным условием
    Результат3 = НайтиПоУсловию(Дерево.Строки, "Зарплата > 50000 И Возраст < 35");
    Сообщить("Сотрудники с зарплатой > 50000 и возрастом < 35:");
    Для Каждого Сотрудник Из Результат3 Цикл
        Сообщить("  " + Сотрудник.Наименование + " - зарплата: " + 
                 Сотрудник.Зарплата + ", возраст: " + Сотрудник.Возраст);
    КонецЦикла;
    
КонецПроцедуры

Функция СоздатьДеревоСотрудников()
    Дерево = Новый ДеревоЗначений;
    Дерево.Колонки.Добавить("Наименование");
    Дерево.Колонки.Добавить("Зарплата", Новый ОписаниеТипов("Число"));
    Дерево.Колонки.Добавить("Возраст", Новый ОписаниеТипов("Число"));
    
    Отдел1 = Дерево.Строки.Добавить();
    Отдел1.Наименование = "Отдел продаж";
    
    Сотр1 = Отдел1.Строки.Добавить();
    Сотр1.Наименование = "Иванов"; Сотр1.Зарплата = 70000; Сотр1.Возраст = 35;
    
    Сотр2 = Отдел1.Строки.Добавить();
    Сотр2.Наименование = "Петров"; Сотр2.Зарплата = 55000; Сотр2.Возраст = 28;
    
    Отдел2 = Дерево.Строки.Добавить();
    Отдел2.Наименование = "Отдел разработки";
    
    Сотр3 = Отдел2.Строки.Добавить();
    Сотр3.Наименование = "Сидоров"; Сотр3.Зарплата = 80000; Сотр3.Возраст = 32;
    
    Сотр4 = Отдел2.Строки.Добавить();
    Сотр4.Наименование = "Кузнецов"; Сотр4.Зарплата = 60000; Сотр4.Возраст = 25;
    
    Возврат Дерево;
КонецФункции

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

Функция ВыполнитьУсловие(Строка, Условие)
    // Упрощенная проверка (в реальном коде используйте Вычислить или Строка.Зарплата > 60000)
    Если Условие = "Зарплата > 60000" Тогда
        Возврат Строка.Зарплата > 60000;
    ИначеЕсли Условие = "Возраст < 30" Тогда
        Возврат Строка.Возраст < 30;
    ИначеЕсли Условие = "Зарплата > 50000 И Возраст < 35" Тогда
        Возврат Строка.Зарплата > 50000 И Строка.Возраст < 35;
    КонецЕсли;
    
    Возврат Ложь;
КонецФункции

Рекурсивное копирование и преобразование дерева

// Создание глубокой копии дерева с возможным преобразованием данных
Процедура РекурсивноеКопированиеДерева()

    Исходное = СоздатьТестовоеДерево();
    
    Сообщить("Исходное дерево:");
    ВывестиДерево(Исходное.Строки, "");
    
    // Копирование с преобразованием (увеличиваем сумму на 10%)
    Копия = СкопироватьДеревоСПреобразованием(Исходное);
    
    Сообщить("Копия с увеличенной на 10% суммой:");
    ВывестиДерево(Копия.Строки, "");
    
    // Копирование только узлов, удовлетворяющих условию
    Фильтрованное = СкопироватьУзлыПоУсловию(Исходное, "Сумма > 500");
    
    Сообщить("Фильтрованное дерево (сумма > 500):");
    ВывестиДерево(Фильтрованное.Строки, "");
    
КонецПроцедуры

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

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

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

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

Функция ВыполнитьУсловиеНаСтроке(Строка, Условие)
    Если Условие = "Сумма > 500" Тогда
        Возврат Строка.Сумма > 500;
    КонецЕсли;
    Возврат Истина;
КонецФункции

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

Рекурсивный расчет агрегированных показателей

// Расчет итогов для каждого узла на основе дочерних элементов
Процедура РасчетАгрегированныхПоказателей()

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

Функция СоздатьДеревоПродаж()
    Дерево = Новый ДеревоЗначений;
    Дерево.Колонки.Добавить("Наименование");
    Дерево.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
    Дерево.Колонки.Добавить("ИтоговаяСумма", Новый ОписаниеТипов("Число"));
    Дерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
    Дерево.Колонки.Добавить("ИтоговоеКоличество", Новый ОписаниеТипов("Число"));
    
    Год2024 = Дерево.Строки.Добавить();
    Год2024.Наименование = "2024 год";
    Год2024.Сумма = 0;
    Год2024.Количество = 0;
    
    Квартал1 = Год2024.Строки.Добавить();
    Квартал1.Наименование = "1 квартал";
    Квартал1.Сумма = 0;
    Квартал1.Количество = 0;
    
    Январь = Квартал1.Строки.Добавить();
    Январь.Наименование = "Январь";
    Январь.Сумма = 50000;
    Январь.Количество = 10;
    
    Февраль = Квартал1.Строки.Добавить();
    Февраль.Наименование = "Февраль";
    Февраль.Сумма = 60000;
    Февраль.Количество = 12;
    
    Квартал2 = Год2024.Строки.Добавить();
    Квартал2.Наименование = "2 квартал";
    Квартал2.Сумма = 0;
    Квартал2.Количество = 0;
    
    Март = Квартал2.Строки.Добавить();
    Март.Наименование = "Март";
    Март.Сумма = 70000;
    Март.Количество = 14;
    
    Возврат Дерево;
КонецФункции

Функция РассчитатьИтогиРекурсивно(СтрокиДерева)
    Для Каждого Строка Из СтрокиДерева Цикл
        
        Если Строка.Строки.Количество() > 0 Тогда
            // Рекурсивно рассчитываем итоги для детей
            РассчитатьИтогиРекурсивно(Строка.Строки);
            
            // Суммируем данные детей
            ИтогСумма = 0;
            ИтогКоличество = 0;
            
            Для Каждого Ребенок Из Строка.Строки Цикл
                // Если у ребенка уже есть итоговые значения, берем их, иначе берем собственные
                Если Ребенок.ИтоговаяСумма > 0 Или Ребенок.Строки.Количество() > 0 Тогда
                    ИтогСумма = ИтогСумма + Ребенок.ИтоговаяСумма;
                    ИтогКоличество = ИтогКоличество + Ребенок.ИтоговоеКоличество;
                Иначе
                    ИтогСумма = ИтогСумма + Ребенок.Сумма;
                    ИтогКоличество = ИтогКоличество + Ребенок.Количество;
                КонецЕсли;
            КонецЦикла;
            
            // Добавляем собственную сумму, если есть
            Строка.ИтоговаяСумма = ИтогСумма + Строка.Сумма;
            Строка.ИтоговоеКоличество = ИтогКоличество + Строка.Количество;
        Иначе
            // Для листовых узлов итоги равны собственным значениям
            Строка.ИтоговаяСумма = Строка.Сумма;
            Строка.ИтоговоеКоличество = Строка.Количество;
        КонецЕсли;
        
    КонецЦикла;
КонецФункции

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

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

Рекурсивное построение дерева из плоского списка

// Построение иерархического дерева из плоской таблицы с использованием рекурсии
Процедура ПостроениеДереваИзСписка()

    // Исходный плоский список
    Таблица = Новый ТаблицаЗначений;
    Таблица.Колонки.Добавить("ID", Новый ОписаниеТипов("Число"));
    Таблица.Колонки.Добавить("ParentID", Новый ОписаниеТипов("Число"));
    Таблица.Колонки.Добавить("Наименование");
    Таблица.Колонки.Добавить("Значение", Новый ОписаниеТипов("Число"));
    
    Таблица.Добавить().ID = 1; Таблица[0].ParentID = 0; Таблица[0].Наименование = "Корень 1"; Таблица[0].Значение = 0;
    Таблица.Добавить().ID = 2; Таблица[1].ParentID = 1; Таблица[1].Наименование = "Узел 1.1"; Таблица[1].Значение = 100;
    Таблица.Добавить().ID = 3; Таблица[2].ParentID = 1; Таблица[2].Наименование = "Узел 1.2"; Таблица[2].Значение = 200;
    Таблица.Добавить().ID = 4; Таблица[3].ParentID = 2; Таблица[3].Наименование = "Лист 1.1.1"; Таблица[3].Значение = 50;
    Таблица.Добавить().ID = 5; Таблица[4].ParentID = 2; Таблица[4].Наименование = "Лист 1.1.2"; Таблица[4].Значение = 75;
    Таблица.Добавить().ID = 6; Таблица[5].ParentID = 0; Таблица[5].Наименование = "Корень 2"; Таблица[5].Значение = 0;
    Таблица.Добавить().ID = 7; Таблица[6].ParentID = 6; Таблица[6].Наименование = "Узел 2.1"; Таблица[6].Значение = 300;
    
    // Создаем соответствие для быстрого доступа к строкам по ID
    ТаблицаПоИд = Новый Соответствие;
    Для Каждого СтрокаТаблицы Из Таблица Цикл
        ТаблицаПоИд.Вставить(СтрокаТаблицы.ID, СтрокаТаблицы);
    КонецЦикла;
    
    // Создаем дерево
    Дерево = Новый ДеревоЗначений;
    Дерево.Колонки.Добавить("Наименование");
    Дерево.Колонки.Добавить("Значение", Новый ОписаниеТипов("Число"));
    Дерево.Колонки.Добавить("ID", Новый ОписаниеТипов("Число"));
    
    // Добавляем корневые элементы
    Для Каждого СтрокаТаблицы Из Таблица Цикл
        Если СтрокаТаблицы.ParentID = 0 Тогда
            ДобавитьВеткуРекурсивно(Дерево.Строки, СтрокаТаблицы, ТаблицаПоИд);
        КонецЕсли;
    КонецЦикла;
    
    // Выводим результат
    Сообщить("Построенное иерархическое дерево:");
    ВывестиПостроенноеДерево(Дерево.Строки, "");
    
КонецПроцедуры

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

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

Рекурсивное удаление узлов по условию

// Удаление всех узлов, удовлетворяющих условию (с возможным удалением родителей)
Процедура РекурсивноеУдалениеУзлов()

    Дерево = СоздатьДеревоДляУдаления();
    
    Сообщить("Исходное дерево:");
    ВывестиДеревоСПометками(Дерево.Строки, "");
    
    // Удаляем узлы с суммой = 0 (пустые категории) без удаления родителей
    УдалитьУзлыПоУсловию(Дерево.Строки, "Сумма = 0", Ложь);
    
    Сообщить("После удаления узлов с суммой = 0 (без удаления родителей):");
    ВывестиДеревоСПометками(Дерево.Строки, "");
    
    // Удаляем узлы с меткой "Удалить" и их родителей, если они остались пустыми
    УдалитьПустыеРодители(Дерево.Строки);
    
    Сообщить("После удаления пустых родителей:");
    ВывестиДеревоСПометками(Дерево.Строки, "");
    
КонецПроцедуры

Функция СоздатьДеревоДляУдаления()
    Дерево = Новый ДеревоЗначений;
    Дерево.Колонки.Добавить("Наименование");
    Дерево.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
    Дерево.Колонки.Добавить("ПометкаУдаления", Новый ОписаниеТипов("Булево"));
    
    Корень1 = Дерево.Строки.Добавить();
    Корень1.Наименование = "Категория 1"; Корень1.Сумма = 0; Корень1.ПометкаУдаления = Истина;
    
    Ребенок1 = Корень1.Строки.Добавить();
    Ребенок1.Наименование = "Товар 1"; Ребенок1.Сумма = 100; Ребенок1.ПометкаУдаления = Ложь;
    
    Корень2 = Дерево.Строки.Добавить();
    Корень2.Наименование = "Категория 2"; Корень2.Сумма = 0; Корень2.ПометкаУдаления = Истина;
    
    Ребенок2 = Корень2.Строки.Добавить();
    Ребенок2.Наименование = "Пустой товар"; Ребенок2.Сумма = 0; Ребенок2.ПометкаУдаления = Истина;
    
    Возврат Дерево;
КонецФункции

Функция УдалитьУзлыПоУсловию(СтрокиДерева, Условие, УдалятьРодителей = Истина)
    Для Инд = СтрокиДерева.Количество() - 1 По 0 Шаг -1 Цикл
        Строка = СтрокиДерева[Инд];
        
        // Рекурсивно обрабатываем дочерние элементы
        Если Строка.Строки.Количество() > 0 Тогда
            УдалитьУзлыПоУсловию(Строка.Строки, Условие, УдалятьРодителей);
        КонецЕсли;
        
        // Проверяем условие удаления
        Если ВыполнитьУсловиеУдаления(Строка, Условие) Тогда
            // Если узел нужно удалить
            УдалитьРодительскуюСтроку(СтрокиДерева, Инд);
        КонецЕсли;
    КонецЦикла;
КонецФункции

Процедура УдалитьПустыеРодители(СтрокиДерева)
    Для Инд = СтрокиДерева.Количество() - 1 По 0 Шаг -1 Цикл
        Строка = СтрокиДерева[Инд];
        
        // Сначала обрабатываем дочерние элементы
        Если Строка.Строки.Количество() > 0 Тогда
            УдалитьПустыеРодители(Строка.Строки);
            
            // Если после удаления детей родитель остался пустым, удаляем его
            Если Строка.Строки.Количество() = 0 И Строка.Сумма = 0 Тогда
                УдалитьРодительскуюСтроку(СтрокиДерева, Инд);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

Функция ВыполнитьУсловиеУдаления(Строка, Условие)
    Если Условие = "Сумма = 0" Тогда
        Возврат Строка.Сумма = 0;
    КонецЕсли;
    Возврат Ложь;
КонецФункции

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

Примечания

// Важные особенности рекурсивного обхода дерева значений:
// 1. Всегда проверяйте условие выхода из рекурсии (termination condition)
// 2. При рекурсивном обходе будьте внимательны с глубиной рекурсии (ограничение ~128-256 уровней)
// 3. Для очень глубоких деревьев используйте итеративный обход с использованием стека
// 4. При удалении узлов обходите дерево с конца (шаг -1) для избежания смещения индексов
// 5. Для кэширования результатов обхода используйте соответствие или массив
// 6. При формировании пути используйте разделитель и накапливайте строку на каждом уровне
// 7. Для агрегации данных (суммы, количества) обрабатывайте снизу вверх (post-order traversal)
// 8. При рекурсивном копировании не забудьте скопировать структуру колонок
// 9. Для поиска по дереву используйте ранний выход при нахождении первого элемента
// 10. Избегайте модификации дерева во время итерации (используйте копию или обратный проход)
// 11. Для отладки рекурсии выводите текущий уровень и имя узла
// 12. Потребление памяти при рекурсии растет с глубиной - учитывайте это для больших деревьев

Поделиться с друзьями
Smirnov code
Добавить комментарий