Как синхронизировать количество веток в Дереве Значений 1С?

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

При работе с иерархическими данными в 1С, представленными в виде объекта ДеревоЗначений, часто возникает задача приведения структуры к единому виду. Рассмотрим ситуацию: у нас есть дерево с фиксированной структурой уровней (например, Категория -> Вид приема пищи -> Блюдо), но в разных родительских ветках может быть разное количество дочерних элементов. Наша цель — сделать так, чтобы на каждом уровне количество дочерних веток у всех параллельных родительских узлов было одинаковым.

Например, если в категории продуктов "А" есть только "Завтрак" и "Обед", а в категории "Б" — "Завтрак", "Обед" и "Ужин", то нам нужно добавить в категорию "А" недостающую ветку, чтобы количество приемов пищи в обеих категориях стало равным трем. То же самое касается и более глубоких уровней, например, количества блюд для каждого приема пищи.

Проблема при попытке решить это "в лоб" с помощью одной рекурсивной функции заключается в том, что можно легко уйти в бесконечный цикл, повторно обходя уже измененные ветки. Наиболее надежным и понятным решением является двухпроходный алгоритм.

Решение 1: Классический двухпроходный алгоритм

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

  1. Первый проход: Сбор информации. На этом этапе мы обходим все дерево и определяем максимальное количество дочерних элементов для каждого уровня и типа родительской ветки. Эту информацию удобно хранить в структуре данных типа Соответствие.
  2. Второй проход: Модификация дерева. Мы снова обходим дерево, но теперь, имея на руках "эталонные" данные из Соответствия, мы сравниваем фактическое количество дочерних элементов в каждой ветке с максимальным и добавляем недостающие пустые строки.

Посмотрим на пример реализации. Предположим, у нас есть дерево, корневые строки которого находятся в переменной СтрокаОрганизации.Строки.

Шаг 1. Сбор максимальных значений

Создадим Соответствие и обойдем дерево, чтобы заполнить его. Для каждого уровня вложенности мы будем использовать свой ключ. Например, для количества приемов пищи в категории ключ будет строкой "ПриемовПищиВКатегории", а для количества блюд в приеме пищи ключом будет само наименование приема пищи (например, "Обед").


// Создаем соответствие для хранения максимальных количеств
Количество = Новый Соответствие;

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

После выполнения этого кода в нашем Соответствии будут храниться примерно такие данные:

Шаг 2. Добавление недостающих строк

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


// Вспомогательная процедура для добавления строк
Процедура ДополнитьСтроки(РодительскаяСтрока, ТребуемоеКоличество)
    Пока РодительскаяСтрока.Строки.Количество() < ТребуемоеКоличество Цикл
        // Добавляем новую пустую строку
        РодительскаяСтрока.Строки.Добавить(); 
    КонецЦикла;
КонецПроцедуры

// Второй проход: дополняем дерево до максимальных значений
Для Каждого Категория Из СтрокаОрганизации.Строки Цикл
    
    // --- Уровень 1: Приемы пищи в категориях ---
    Ключ = "ПриемовПищиВКатегории";
    ТребуемоеКоличество = Количество.Получить(Ключ);
    ДополнитьСтроки(Категория, ТребуемоеКоличество);
    
    // --- Уровень 2: Блюда в приемах пищи ---
    // Важно: обходим коллекцию уже после ее возможного дополнения
    Для Каждого ПриемПищи Из Категория.Строки Цикл
        
        // Пропускаем обработку пустых, только что добавленных строк, если у них нет наименования
        Если НЕ ЗначениеЗаполнено(ПриемПищи.ПриемПищи) Тогда
            Продолжить;
        КонецЕсли;
        
        Ключ = ПриемПищи.ПриемПищи;
        ТребуемоеКоличество = Количество.Получить(Ключ);
        Если ТребуемоеКоличество <> Неопределено Тогда
            ДополнитьСтроки(ПриемПищи, ТребуемоеКоличество);
        КонецЕсли;
        
    КонецЦикла;
    
КонецЦикла;

Решение 2: Синхронизация по составу, а не по количеству

Предыдущий метод решает задачу выравнивания количества строк, но оставляет открытым важный вопрос: что, если нам нужно не просто одинаковое количество, а одинаковый набор дочерних элементов? Например, если в одном "Обеде" есть "Суп" и "Компот", а в другом — "Салат", то простое добавление пустой строки не сделает их идентичными. Структура может "съехать".

Для решения этой задачи алгоритм нужно усложнить.

  1. Первый проход (сбор "золотого набора"). На этом этапе мы собираем не просто максимальное количество, а полный уникальный список всех дочерних элементов для каждого типа родителя. Вместо Соответствия с числами будем использовать Соответствие, где значением будет Массив уникальных наименований.
  2. Второй проход (синхронизация по набору). Во втором проходе для каждой родительской ветки мы будем итерировать по "золотому набору" из Соответствия. Для каждого элемента из набора мы проверяем, есть ли он среди дочерних строк. Если нет — добавляем новую строку с этим наименованием.

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

Альтернативные подходы

Помимо прямого изменения дерева, существуют и другие методы, которые могут быть полезны в зависимости от контекста задачи.

1. Выгрузка в таблицу, обработка и загрузка обратно.

Для очень больших и сложных деревьев рекурсивные обходы могут быть не самыми производительными. Альтернатива — выгрузить все дерево в плоскую ТаблицуЗначений с помощью метода Выгрузить(). Затем, используя запросы или группировки этой таблицы, можно легко собрать всю необходимую информацию о максимальных количествах или "золотых наборах". После этого на основе проанализированных данных создается совершенно новое, уже синхронизированное ДеревоЗначений с нуля.

2. Синхронизация "на лету" при выводе.

Если изменять исходные данные нежелательно, а синхронизированный вид нужен только для отображения (например, в отчете или на форме), можно не менять само ДеревоЗначений. Вместо этого, логику синхронизации можно реализовать на уровне вывода. При формировании отчета или заполнении табличного поля на форме вы можете сначала получить "эталонные" данные (максимальные количества или наборы), а затем при выводе каждой ветки динамически добавлять пустые строки или ячейки в результирующую таблицу, сохраняя исходные данные нетронутыми.

← На главную