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