Как вставить строки в новое Дерево Значений с сохранением иерархии при фильтрации

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

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

Метод 1: Использование вспомогательной колонки «Сумма» или «Флаг»

Рассмотрим ситуацию, когда нам нужно определить, стоит ли добавлять строку-родителя в новое дерево. Мы не можем принять решение, пока не проверим всех её «детей» на всех уровнях вложенности. Одним из самых простых и надежных способов является предварительный расчет признака необходимости строки.

Алгоритм действий:

  1. Добавим в исходное дерево значений временную колонку НужноВыводить с типом Булево или Число.
  2. Выполним рекурсивный обход дерева «снизу вверх». Это позволит нам передать признак наличия подходящего элемента от потомков к предкам.
  3. На каждом уровне установим НужноВыводить = Истина, если у самой строки стоит флаг НеТиповой ИЛИ если хотя бы у одной дочерней строки НужноВыводить равно Истина.
  4. После этого вторым проходом перенесем только помеченные строки в новое дерево.

Проанализируем пример кода для расчета такой колонки:


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

Метод 2: Рекурсивное удаление лишних ветвей (Метод «Снизу вверх»)

Этот способ считается наиболее элегантным, так как он не требует создания нового объекта дерева, а работает с копией существующего, удаляя все ненужное. Выясним причину, по которой обычное удаление «сверху вниз» не работает: если мы удалим родителя, мы потеряем доступ к его детям, которые могли быть нам нужны.

Пошаговая реализация:

  1. Создадим полную копию исходного дерева с помощью метода Скопировать().
  2. Запустим рекурсивную процедуру обхода.
  3. Внутри процедуры сначала вызываем её же для коллекции Строки текущего узла.
  4. После возврата из рекурсии (когда обработаны все уровни ниже), проверяем: если у строки нет флага НеТиповой и у неё не осталось дочерних строк (они были удалены на шаге ниже), удаляем текущую строку из коллекции.

Рассмотрим реализацию на языке 1С:


&НаСервере
Процедура ФильтроватьДеревоРекурсивно(КоллекцияСтрок)
    Индекс = КоллекцияСтрок.Количество() - 1;
    
    Пока Индекс >= 0 Цикл
        ТекущаяСтрока = КоллекцияСтрок[Индекс];
        
        // Уходим вглубь
        ФильтроватьДеревоРекурсивно(ТекущаяСтрока.Строки);
        
        // Условие удаления: не соответствует фильтру И нет подходящих детей
        Если Не ТекущаяСтрока.НеТиповой И ТекущаяСтрока.Строки.Количество() = 0 Тогда
            КоллекцияСтрок.Удалить(ТекущаяСтрока);
        КонецЕсли;
        
        Индекс = Индекс - 1;
    КонецЦикла;
КонецПроцедуры

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

Метод 3: Использование объекта «Соответствие» для кэширования родителей

Если дерево очень большое, многократные поиски и обходы могут замедлить систему. Рассмотрим альтернативный вариант: создание нового дерева «на лету» только при обнаружении целевого элемента. Чтобы не дублировать родителей, мы будем использовать Соответствие, где ключом будет ссылка на строку исходного дерева, а значением — созданная строка в новом дереве.

Рассмотрим логику процесса:

Когда мы находим строку с флагом НеТиповой, нам нужно убедиться, что вся цепочка её родителей уже создана в новом дереве. Мы рекурсивно поднимаемся вверх к корню. Если родителя еще нет в нашем «кэше» (в объекте Соответствие), мы его создаем и запоминаем.

Проанализируем пример кода с использованием кэширования:


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

Такой подход гарантирует, что дерево будет построено за один проход по целевым элементам, а структура иерархии останется идентичной оригиналу.

Метод 4: Система компоновки данных (СКД)

Если ваша задача — просто вывести отфильтрованное дерево пользователю в отчет или на форму, проанализируем возможности Системы компоновки данных. СКД обладает встроенными механизмами работы с иерархией.

Как это работает:

  1. Вы передаете дерево значений (или таблицу, на основе которой оно строится) в СКД как внешний набор данных.
  2. В настройках структуры указываете группировку с типом «Иерархия».
  3. При наложении отбора по полю НеТиповой = Истина, СКД автоматически выведет всех родителей для тех строк, которые попали в выборку, при условии правильной настройки иерархических связей.

Этот метод является наиболее производительным для больших объемов данных, так как оптимизирован на уровне платформы 1С v8.3.

Сравнение методов и рекомендации

Подведем итог и выясним, какой метод выбрать в зависимости от условий:

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

← На главную