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