Обход элементов массива во встроенном языке 1С 8.3: от «Для Каждого» до коллекций-заменителей

· 10 мин

Содержание

Перебрать массив в 1С можно тремя способами. Для Каждого Элемент Из Массив Цикл подходит, когда нужны только значения. Для Индекс = 0 По Массив.ВГраница() Цикл нужен, когда важен номер элемента или элементы меняются по месту. Цикл Пока с убывающим индексом нужен, когда элементы удаляются прямо во время обхода.

Всё остальное (обход соответствия, отбор, суммирование, поиск дублей) строится на этих трёх циклах или на соседних коллекциях: ТаблицаЗначений, Соответствие, запрос. Встроенных функций вида «карта» или «фильтр» с лямбдами в языке 1С нет. Ниже показано, чем их заменяют на практике.

Цикл «Для Каждого»: самый короткий перебор массива

Это основной способ. Переменная цикла по очереди получает каждое значение массива, начиная с индекса 0.

Товары = Новый Массив;
Товары.Добавить("Бумага А4");
Товары.Добавить("Скрепки");
Товары.Добавить("Маркер");

Для Каждого Товар Из Товары Цикл
    Сообщить(Товар);
КонецЦикла;

Что важно знать про этот цикл:

  • ✅ Пустой массив не вызывает ошибку: тело цикла просто не выполнится ни разу.
  • ✅ Прервать выходит из цикла, Продолжить переходит к следующему элементу.
  • ⚠️ Номера текущего элемента цикл не даёт. Если он нужен, заведите счётчик или берите индексный цикл.
  • ⚠️ Присваивание переменной цикла не меняет массив. Товар = ВРег(Товар) изменит только локальную копию значения.

Последний пункт подводит чаще всего. Строка, число и дата хранятся в массиве как значения. Переменная Товар получает копию, и запись в неё до массива не доходит.

С объектами ситуация другая. Если в массиве лежат структуры или строки таблицы значений, переменная указывает на тот же объект. Тогда Строка.Цена = 100 изменит данные в самой коллекции:

Для Каждого Позиция Из МассивСтруктур Цикл
    Позиция.Сумма = Позиция.Количество * Позиция.Цена; // меняет исходную структуру
КонецЦикла;

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

Перебор массива по индексу: когда нужен номер элемента

Индексы массива в 1С начинаются с нуля. Последний индекс возвращает метод ВГраница(), он равен Количество() - 1. У пустого массива ВГраница() вернёт -1, поэтому цикл ниже для пустого массива не выполнится ни разу.

Для Индекс = 0 По Товары.ВГраница() Цикл
    Товары[Индекс] = ВРег(Товары[Индекс]); // запись по индексу меняет сам массив
КонецЦикла;

Индексный цикл нужен в четырёх случаях:

  1. 🔢 Нужно заменить значения в массиве по месту.
  2. 🔗 Нужно сравнить элемент с соседним: Товары[Индекс] и Товары[Индекс + 1].
  3. 🧮 Два массива одинаковой длины обходятся синхронно: Коды[Индекс] и Наименования[Индекс].
  4. 🧾 Номер позиции нужен в выводе, например «Строка 3 из 10».

Шага у цикла Для в 1С нет. Чтобы взять каждый второй элемент, используйте условие с остатком от деления %:

Для Индекс = 0 По Товары.ВГраница() Цикл
    Если Индекс % 2 = 0 Тогда
        Сообщить(СтрШаблон("%1: %2", Индекс, Товары[Индекс]));
    КонецЕсли;
КонецЦикла;

СтрШаблон сам приводит число к строке. При ручной склейке через + используйте Формат(Индекс, "ЧН=0; ЧГ=0"), иначе после 999 в выводе появится разделитель групп разрядов.

Обратный обход с удалением элементов

Когда элементы удаляются, идите с конца. Удаление сдвигает влево только хвост массива, а он уже обработан.

Индекс = Товары.ВГраница();
Пока Индекс >= 0 Цикл
    Если ПустаяСтрока(Товары[Индекс]) Тогда
        Товары.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Прогоним пример на массиве ["А", "", "", "Б"]. Индекс 3 — «Б», не удаляем. Индекс 2 — пустая строка, удаляем, массив стал ["А", "", "Б"]. Индекс 1 — снова пустая, удаляем: ["А", "Б"]. Индекс 0 — «А». Обе пустые строки удалены, пропусков нет.

Не пересчитывайте индекс от Количество() внутри цикла с удалением: после каждого Удалить() количество уменьшается, и формула вида Количество() - Сч начнёт перескакивать через элементы.

Многомерный массив

Конструктор Новый Массив(3, 4) создаёт массив из трёх массивов по четыре элемента. Перебирается он вложенными циклами:

Матрица = Новый Массив(3, 4);
Для Строка = 0 По Матрица.ВГраница() Цикл
    Для Колонка = 0 По Матрица[Строка].ВГраница() Цикл
        Матрица[Строка][Колонка] = Строка * 10 + Колонка;
    КонецЦикла;
КонецЦикла;

Элемент Матрица[2][3] получит значение 23. Для табличных данных удобнее таблица значений: у неё есть имена колонок и встроенный поиск.

Как перебрать Соответствие и Структуру

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

Остатки = Новый Соответствие;
Остатки.Вставить("Бумага А4", 120);
Остатки.Вставить("Скрепки", 0);

Для Каждого Пара Из Остатки Цикл
    Если Пара.Значение = 0 Тогда
        Сообщить(Пара.Ключ + " закончились");
    КонецЕсли;
КонецЦикла;

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

Три правила для обхода соответствия:

  • 🔀 Не стройте логику на порядке элементов. Нужна сортировка — выгрузите ключи в массив или таблицу значений и отсортируйте там.
  • 🚫 Не удаляйте и не добавляйте ключи во время обхода. Сначала соберите ключи в массив, затем удалите их отдельным циклом.
  • 🔎 Чтобы прочитать одно значение, обходить соответствие не нужно: Остатки.Получить("Скрепки") вернёт значение или Неопределено.

Удаление по условию выглядит так:

КУдалению = Новый Массив;
Для Каждого Пара Из Остатки Цикл
    Если Пара.Значение = 0 Тогда
        КУдалению.Добавить(Пара.Ключ);
    КонецЕсли;
КонецЦикла;

Для Каждого Ключ Из КУдалению Цикл
    Остатки.Удалить(Ключ);
КонецЦикла;

Отбор, преобразование и свёртка массива без лямбд

В JavaScript или Python перебор часто заменяют функциями map, filter и reduce. Во встроенном языке 1С таких методов у массива нет, анонимных функций тоже нет. Задачи те же, но решаются иначе.

ЗадачаКак в других языкахКак в 1С
Преобразовать каждый элементmapЦикл с Добавить() в новый массив
Отобрать по условиюfilterЦикл с Если, либо НайтиСтроки() у таблицы значений
Свести к одному значениюreduceЦикл с накопителем, либо Итог() у таблицы значений
Склеить в строкуjoinСтрСоединить(Массив, ", ")
Оставить уникальныеSetСоответствие как множество или Свернуть() у таблицы

Преобразование и отбор циклом

Ручной цикл — самый понятный вариант. Он читается без комментариев и одинаково работает на клиенте и на сервере.

// «filter» + «map»: суммы больше 1000 с НДС 20%
СуммыСНДС = Новый Массив;
Для Каждого Сумма Из Суммы Цикл
    Если Сумма > 1000 Тогда
        СуммыСНДС.Добавить(Окр(Сумма * 1.2, 2));
    КонецЕсли;
КонецЦикла;

Для массива [500, 1500, 2000] получится [1800, 2400]: 1500 × 1,2 = 1800, 2000 × 1,2 = 2400.

Свёртка через таблицу значений

Когда записей много и у них несколько полей, удобнее таблица значений. У неё есть готовые методы, которые заменяют отбор и свёртку:

// «filter»: строки со складом «Основной»
Найденные = ТаблицаОстатков.НайтиСтроки(Новый Структура("Склад", "Основной"));

// «reduce»: сумма по колонке
ВсегоКоличество = ТаблицаОстатков.Итог("Количество");

// Группировка с суммированием
ТаблицаОстатков.Свернуть("Номенклатура", "Количество");

// Колонка в массив и обратно
МассивНоменклатуры = ТаблицаОстатков.ВыгрузитьКолонку("Номенклатура");

НайтиСтроки() возвращает массив строк таблицы. Его снова обходят через Для Каждого. ВыгрузитьКолонку() возвращает обычный массив значений.

Готовые функции БСП

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

Не эмулируйте лямбды через Выполнить() или Вычислить() со строкой кода. Такой код медленный, не проверяется синтаксическим контролем и недоступен в ряде режимов, например в безопасном режиме и в веб-клиенте.

Ускоряем перебор больших массивов

Сам цикл по массиву из 100 000 чисел выполняется быстро. Медленным перебор делают операции внутри тела цикла. Вот что проверить в первую очередь.

1. Запрос и обращение к реквизитам внутри цикла

Самая дорогая ошибка: в каждой итерации выполнять запрос или читать реквизит ссылки через точку. Товар.Артикул для ссылки — это обращение к базе данных. На 10 000 элементах это 10 000 обращений.

Правильно — один запрос с массивом в параметре:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Номенклатура.Ссылка КАК Ссылка,
|    Номенклатура.Артикул КАК Артикул
|ИЗ
|    Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|    Номенклатура.Ссылка В (&СписокТоваров)";
Запрос.УстановитьПараметр("СписокТоваров", МассивТоваров);

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    // обработка Выборка.Ссылка, Выборка.Артикул
КонецЦикла;

2. Поиск значения в массиве внутри цикла

Массив.Найти(Значение) просматривает элементы по порядку. Если внутри цикла по одному массиву вы вызываете Найти() во втором массиве, время растёт как произведение их размеров: 10 000 × 10 000 = 100 млн сравнений.

Решение — один раз переложить второй массив в соответствие и проверять наличие через Получить():

Множество = Новый Соответствие;
Для Каждого Значение Из Второй Цикл
    Множество.Вставить(Значение, Истина);
КонецЦикла;

Для Каждого Значение Из Первый Цикл
    Если Множество.Получить(Значение) = Истина Тогда
        // значение есть в обоих массивах
    КонецЕсли;
КонецЦикла;

3. Склейка строки и вставка в начало

ОперацияКак медленноКак быстро
Сборка длинной строкиТекст = Текст + Элемент в циклеСобрать массив и вызвать СтрСоединить()
Добавление элементовВставить(0, Значение): сдвигает весь массивДобавить() в конец, при необходимости обойти с конца
Известный заранее размерМного вызовов Добавить()Новый Массив(N) и запись по индексу
Передача с сервера на клиентМассив объектов и ссылок с лишними даннымиТолько нужные простые значения

Количество() и ВГраница() в заголовке цикла Для вычисляются один раз, до первой итерации. Выносить их в переменную ради скорости не нужно. В цикле Пока условие проверяется на каждой итерации, но вызов метода массива дешёвый.

Частые ошибки при обходе массива

Ниже ошибки, которые приводят к исключениям или неверным данным. Про удаление внутри Для Каждого и запросы в цикле сказано выше, здесь — остальное.

Диагностика: выберите симптом

Какой способ перебора массива выбрать

Сведём способы в одну таблицу. Ориентируйтесь на задачу, а не на привычку.

СпособКогда братьОграничения
Для Каждого … ИзЧтение значений, вызов процедуры для каждого элемента, изменение полей объектовНет индекса, нельзя удалять элементы
Для Индекс = 0 По ВГраница()Замена значений по месту, сосед или параллельный массив, номер позицииЛегко выйти за границу при работе с соседом
Пока с убывающим индексомУдаление элементов по условиюНужно не забыть уменьшить индекс
Таблица значенийМного полей, отбор, итоги, группировкаТяжелее массива, сериализуется в форму сложнее
СоответствиеПроверка наличия, уникальность, доступ по ключуПорядок обхода не гарантирован
ЗапросДанные из базы, связь с реквизитами ссылокРаботает только на сервере

Короткое правило: начинайте с Для Каждого. Переходите на индексный цикл, только когда без номера элемента не обойтись. Удаляйте элементы обходом с конца. Если перебор нужен ради поиска или подсчёта по данным базы, сначала посмотрите, не решит ли задачу один запрос.

Частые вопросы

Можно ли перебрать массив в обратном порядке без удаления элементов?
Да. Возьмите Для Сч = 0 По Массив.ВГраница() Цикл и читайте Массив[Массив.ВГраница() - Сч]. Без удаления размер массива не меняется, поэтому формула даёт верный индекс.
Как узнать, что массив пустой, до начала цикла?
Проверьте Массив.Количество() = 0. Сам цикл на пустом массиве ошибок не даёт, проверка нужна, только если для пустого случая есть отдельная логика.
Как отсортировать массив перед перебором?
У массива нет метода сортировки. Переложите значения в СписокЗначений и вызовите СортироватьПоЗначению() или загрузите их в колонку таблицы значений и вызовите Сортировать().
Как выйти сразу из двух вложенных циклов?
Прервать выходит только из текущего цикла. Заведите флаг: во внутреннем цикле установите его и вызовите Прервать, во внешнем после внутреннего проверьте флаг и тоже выйдите. Другой вариант — вынести циклы в функцию и выйти через Возврат.
Работает ли перебор массива одинаково на клиенте и на сервере?
Синтаксис циклов одинаковый. Отличаются типы данных: на тонком клиенте нет таблицы значений и запросов, поэтому сложную обработку делайте на сервере, а на клиент передавайте результат.