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