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