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