Как получить декартово произведение подчиненных строк дерева значений в 1С?

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

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

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

Решение 1. Итеративное накопление комбинаций через вспомогательный массив

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

Рассмотрим подробнее логику работы:

  1. Создаем пустой массив, в котором будут накапливаться промежуточные результаты.
  2. Для первой группы элементов просто копируем все ее подчиненные строки в результирующий массив.
  3. Для каждой последующей группы запускаем двойной цикл: берем каждую уже сформированную комбинацию из массива и конкатенируем ее поочередно со всеми подчиненными строками текущей группы дерева.
  4. Заменяем предыдущий массив на вновь сформированный и переходим к следующему узлу дерева.

Посмотрим на практический пример программного кода:


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

Процедура ВыполнитьФормирование()
    // Инициализируем тестовое дерево значений
    ДЗ = Новый ДеревоЗначений;
    ДЗ.Колонки.Добавить("Значение", Новый ОписаниеТипов("Строка"));
    
    Для Ид = 1 По 3 Цикл
        СтрокаДЗ = ДЗ.Строки.Добавить();
        СтрокаДЗ.Значение = "Группа_" + Ид;
        Для Ид1 = 1 По 3 Цикл
            Подстрока = СтрокаДЗ.Строки.Добавить();
            Подстрока.Значение = "Значение_" + Ид + "_" + Ид1;
        КонецЦикла;
    КонецЦикла;
    
    // Выполняем обход групп верхнего уровня
    МассивРезультат = Новый Массив;
    Для Каждого СтрокаГруппы Из ДЗ.Строки Цикл
        СформироватьМассивКомбинаций(МассивРезультат, СтрокаГруппы.Строки);
    КонецЦикла;
    
    Сообщить("Количество полученных комбинаций: " + МассивРезультат.Количество());
КонецПроцедуры

Решение 2. Формирование типизированной Таблицы Значений

В реальных прикладных задачах склеивание данных в одну текстовую строку через запятую не всегда удобно: теряются ссылки на объекты базы данных, числа и даты, а также усложняется дальнейшая обработка. Проанализируем, как сформировать объект ТаблицаЗначений, где каждому уровню группировки соответствует отдельная типизированная колонка.

Разберем по шагам создание такой таблицы:

  1. Определяем количество родительских строк в исходном объекте ДеревоЗначений.
  2. Создаем объект ТаблицаЗначений и программно добавляем колонки методом ТЗ.Колонки.Добавить() с соответствующими типами.
  3. При генерации каждой итоговой комбинации создаем строку методом ТЗ.Добавить() и записываем значения по индексам или именам колонок, сохраняя оригинальные типы данных.

Посмотрим на пример рекурсивного заполнения типизированной таблицы значений (метод обхода с возвратом):


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

Решение 3. Использование механизма запросов (декартово соединение)

Если группы данных можно предварительно выгрузить в плоские таблицы значений, перемножение наборов эффективно выполняется средствами языка запросов 1С. Для этого списки передаются во временные таблицы через объект МенеджерВременныхТаблиц, а в секции ИЗ таблицы перечисляются через запятую (либо соединяются через условие ПО ИСТИНА):


ВЫБРАТЬ
    Т1.Значение КАК Значение1,
    Т2.Значение КАК Значение2,
    Т3.Значение КАК Значение3
ИЗ
    &ТаблицаГруппа1 КАК Т1,
    &ТаблицаГруппа2 КАК Т2,
    &ТаблицаГруппа3 КАК Т3

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

Важные ограничения и особенности реализации

Разберем ключевые технические нюансы, с которыми можно столкнуться при использовании подобных алгоритмов:

  1. Комбинаторный взрыв и потребление памяти. Количество результирующих строк рассчитывается как произведение мощностей всех групп. Например, при наличии всего 6 групп по 10 элементов в каждой получается $10^6$ (один миллион) строк. Поскольку объект ТаблицаЗначений целиком загружается в оперативную память сервера или клиента, неконтролируемый рост данных вызовет ошибку «Недостаточно памяти».
  2. Проблема пустых подгрупп. Если хотя бы одна из родительских строк дерева не содержит подчиненных строк (мощность равна 0), классическое декартово произведение даст пустой результат. Всегда проверяйте коллекции подчиненных строк методом Количество(), чтобы предусмотреть логику пропуска пустых веток либо подстановку пустого значения.
  3. Контекст выполнения и управляемые формы. Обратите внимание, что объекты ДеревоЗначений и ТаблицаЗначений не поддерживаются на клиенте в режиме тонкого и веб-клиента. В управляемом интерфейсе дерево отображается в виде объекта ДанныеФормыДерево. Любую комбинаторную обработку и конвертацию данных необходимо выполнять на стороне сервера (директива &НаСервере), преобразуя данные формы с помощью метода ДанныеФормыВЗначение().
← На главную