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