Обход элементов массива во встроенном языке 1С 8.3: от «Для Каждого» до коллекций-заменителей
Содержание
Перебрать массив в 1С можно тремя способами. Для Каждого Элемент Из Массив Цикл подходит, когда нужны только значения. Для Индекс = 0 По Массив.ВГраница() Цикл нужен, когда важен номер элемента или элементы меняются по месту. Цикл Пока с убывающим индексом нужен, когда элементы удаляются прямо во время обхода.
Всё остальное (обход соответствия, отбор, суммирование, поиск дублей) строится на этих трёх циклах или на соседних коллекциях: ТаблицаЗначений, Соответствие, запрос. Встроенных функций вида «карта» или «фильтр» с лямбдами в языке 1С нет. Ниже показано, чем их заменяют на практике.
Цикл «Для Каждого»: самый короткий перебор массива
Это основной способ. Переменная цикла по очереди получает каждое значение массива, начиная с индекса 0.
Товары = Новый Массив;
Товары.Добавить("Бумага А4");
Товары.Добавить("Скрепки");
Товары.Добавить("Маркер");
Для Каждого Товар Из Товары Цикл
Сообщить(Товар);
КонецЦикла;
Что важно знать про этот цикл:
- ✅ Пустой массив не вызывает ошибку: тело цикла просто не выполнится ни разу.
- ✅
Прерватьвыходит из цикла,Продолжитьпереходит к следующему элементу. - ⚠️ Номера текущего элемента цикл не даёт. Если он нужен, заведите счётчик или берите индексный цикл.
- ⚠️ Присваивание переменной цикла не меняет массив.
Товар = ВРег(Товар)изменит только локальную копию значения.
Последний пункт подводит чаще всего. Строка, число и дата хранятся в массиве как значения. Переменная Товар получает копию, и запись в неё до массива не доходит.
С объектами ситуация другая. Если в массиве лежат структуры или строки таблицы значений, переменная указывает на тот же объект. Тогда Строка.Цена = 100 изменит данные в самой коллекции:
Для Каждого Позиция Из МассивСтруктур Цикл
Позиция.Сумма = Позиция.Количество * Позиция.Цена; // меняет исходную структуру
КонецЦикла;
Удалять элементы внутри Для Каждого нельзя. Цикл перескочит через элемент, который встал на место удалённого, и часть данных останется необработанной. Для удаления есть отдельный приём, он описан ниже.
Перебор массива по индексу: когда нужен номер элемента
Индексы массива в 1С начинаются с нуля. Последний индекс возвращает метод ВГраница(), он равен Количество() - 1. У пустого массива ВГраница() вернёт -1, поэтому цикл ниже для пустого массива не выполнится ни разу.
Для Индекс = 0 По Товары.ВГраница() Цикл
Товары[Индекс] = ВРег(Товары[Индекс]); // запись по индексу меняет сам массив
КонецЦикла;
Индексный цикл нужен в четырёх случаях:
- 🔢 Нужно заменить значения в массиве по месту.
- 🔗 Нужно сравнить элемент с соседним:
Товары[Индекс]иТовары[Индекс + 1]. - 🧮 Два массива одинаковой длины обходятся синхронно:
Коды[Индекс]иНаименования[Индекс]. - 🧾 Номер позиции нужен в выводе, например «Строка 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) и запись по индексу |
| Передача с сервера на клиент | Массив объектов и ссылок с лишними данными | Только нужные простые значения |
Количество() и ВГраница() в заголовке цикла Для вычисляются один раз, до первой итерации. Выносить их в переменную ради скорости не нужно. В цикле Пока условие проверяется на каждой итерации, но вызов метода массива дешёвый.
Частые ошибки при обходе массива
Ниже ошибки, которые приводят к исключениям или неверным данным. Про удаление внутри Для Каждого и запросы в цикле сказано выше, здесь — остальное.
Количество() вместо ВГраница() либо обращается к Массив[Индекс + 1] на последнем элементе. Ведите цикл до ВГраница(), а сравнение с соседом — до ВГраница() - 1.Неопределено: массив создан через Новый Массив(N), но не все элементы заполнены. Проверьте элемент перед обращением к его полям или заполняйте массив только через Добавить().ФиксированныйМассив, например из параметров формы или из ответа общего модуля с повторным использованием значений. Создайте изменяемую копию: Новый Массив(ФиксированныйМассив).Для Каждого присваивалось значение переменной цикла, а элементы массива — строки или числа. Переделайте цикл на индексный и пишите в Массив[Индекс].&НаСервере или храните в реквизите формы.Какой способ перебора массива выбрать
Сведём способы в одну таблицу. Ориентируйтесь на задачу, а не на привычку.
| Способ | Когда брать | Ограничения |
|---|---|---|
Для Каждого … Из | Чтение значений, вызов процедуры для каждого элемента, изменение полей объектов | Нет индекса, нельзя удалять элементы |
Для Индекс = 0 По ВГраница() | Замена значений по месту, сосед или параллельный массив, номер позиции | Легко выйти за границу при работе с соседом |
Пока с убывающим индексом | Удаление элементов по условию | Нужно не забыть уменьшить индекс |
| Таблица значений | Много полей, отбор, итоги, группировка | Тяжелее массива, сериализуется в форму сложнее |
| Соответствие | Проверка наличия, уникальность, доступ по ключу | Порядок обхода не гарантирован |
| Запрос | Данные из базы, связь с реквизитами ссылок | Работает только на сервере |
Короткое правило: начинайте с Для Каждого. Переходите на индексный цикл, только когда без номера элемента не обойтись. Удаляйте элементы обходом с конца. Если перебор нужен ради поиска или подсчёта по данным базы, сначала посмотрите, не решит ли задачу один запрос.
Частые вопросы
Можно ли перебрать массив в обратном порядке без удаления элементов?
Для Сч = 0 По Массив.ВГраница() Цикл и читайте Массив[Массив.ВГраница() - Сч]. Без удаления размер массива не меняется, поэтому формула даёт верный индекс.Как узнать, что массив пустой, до начала цикла?
Массив.Количество() = 0. Сам цикл на пустом массиве ошибок не даёт, проверка нужна, только если для пустого случая есть отдельная логика.Как отсортировать массив перед перебором?
СписокЗначений и вызовите СортироватьПоЗначению() или загрузите их в колонку таблицы значений и вызовите Сортировать().Как выйти сразу из двух вложенных циклов?
Прервать выходит только из текущего цикла. Заведите флаг: во внутреннем цикле установите его и вызовите Прервать, во внешнем после внутреннего проверьте флаг и тоже выйдите. Другой вариант — вынести циклы в функцию и выйти через Возврат.