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