Как найти максимальный уровень вложенности строк дерева значений и вывести его на форму?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Разбор типичных ошибок при рекурсивном обходе дерева

Проанализируем ситуацию, представленную в вопросе. Основная проблема пользователя заключалась в неправильном обращении к коллекции строк и ошибке в логике рекурсии. В первой итерации кода возникла ошибка Поле объекта не обнаружено (СтрокиДЗ), так как обращение шло к несуществующему свойству. Последующая попытка исправления через СтрокиДЗ[0].Строки также была ошибочной, так как такой код анализировал только первую ветку дерева, игнорируя остальные узлы на том же уровне.

Для корректного решения нам необходимо реализовать обход всех строк в каждой коллекции. Давайте рассмотрим правильный подход к написанию рекурсивной функции.

Вариант 1: Классический рекурсивный обход коллекции строк

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

Рассмотрим пример реализации на стороне сервера:


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

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

В данном примере функция корректно обходит каждую ветку. Важно помнить: если дерево пустое, функция вернет 0. Если есть только корневые элементы без детей — вернет 1.

Вариант 2: Использование встроенного метода Уровень()

Платформа 1С предоставляет встроенный метод Уровень() для объекта СтрокаДереваЗначений. Этот метод возвращает индекс вложенности строки (0 для самого верхнего уровня, 1 для подчиненного и так далее). Мы можем оптимизировать наш алгоритм, просто найдя строку с максимальным значением этого метода.

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


&НаСервере
Процедура НайтиМаксимальныйУровеньЧерезМетодОбъекта()
    
    ДеревоОбъект = РеквизитФормыВЗначение("ДеревоЗначений");
    ПеременнаяМаксУровень = -1; // -1, если дерево пустое
    
    ОбходДереваДляПоискаУровня(ДеревоОбъект.Строки, ПеременнаяМаксУровень);
    
    // Так как метод Уровень() возвращает индекс с 0, для получения 
    // общего количества уровней прибавляем 1
    Объект.Уровень = ПеременнаяМаксУровень + 1;
    
КонецПроцедуры

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

Вариант 3: Работа с ДанныеФормыДерево без конвертации

Конвертация РеквизитФормыВЗначение может быть ресурсоемкой операцией для очень больших деревьев. Если нам нужно только посчитать уровень, мы можем обойти коллекцию ДанныеФормыДерево напрямую. Основное отличие заключается в том, что вместо свойства Строки у элементов используется метод ПолучитьЭлементы().

Проанализируем код для работы напрямую с реквизитом формы:


&НаСервере
Процедура РассчитатьБезКонвертации()
    
    // Обращаемся напрямую к реквизиту формы "ДеревоЗначений"
    КоллекцияВерхнегоУровня = ДеревоЗначений.ПолучитьЭлементы();
    МаксимальныйУровень = 0;
    
    Для Каждого Элемент Из КоллекцияВерхнегоУровня Цикл
        ПроверитьУровеньРекурсивно(Элемент, МаксимальныйУровень);
    КонецЦикла;
    
    Объект.Уровень = МаксимальныйУровень;
    
КонецПроцедуры

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

Вариант 4: Определение уровня через Запрос

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

Посмотрим на логику примера запроса:


// Пример фрагмента запроса
"ВЫБРАТЬ
// ... поля ...
// ИЕРАРХИЯ справочника
ИТОГИ ПО
    Ссылка ИЕРАРХИЯ"

При выгрузке результата такого запроса в ДеревоЗначений, платформа автоматически строит структуру. Если вам заранее известно, что иерархия строится по справочнику, можно использовать функцию УРОВЕНЬ() прямо в запросе для определения глубины каждой записи, а затем просто найти МАКСИМУМ по этой колонке в полученной таблице.

Важные рекомендации

  1. Производительность: Если в вашем дереве тысячи строк, рекурсия может вызвать замедление работы интерфейса. Старайтесь выполнять расчет только в моменты изменения структуры дерева (например, после загрузки данных или при добавлении строк).
  2. Ограничение стека: Помните, что рекурсия имеет предел глубины (стек вызовов). Для обычных бизнес-задач (5–10 уровней) это не проблема, но для деревьев с глубиной 100+ уровней стоит рассмотреть итерационный обход с использованием вспомогательного массива в качестве стека.
  3. Визуализация: Не забудьте обновить элемент формы после расчета. Если вы меняете реквизит Объект.Уровень, связанное с ним поле ввода на форме обновится автоматически.

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

← На главную