Циклы по массиву во встроенном языке 1С 8.3: от «Для Каждого» до хеш-поиска

· 9 мин

Содержание

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

Остальные способы — вариации этих двух: цикл Пока, обход Соответствия и Структуры через пары «ключ — значение», «фильтр» и «свёртка» своими функциями. На больших массивах скорость решает не выбор цикла, а то, что происходит внутри: поиск, запросы и серверные вызовы.

Что нужно знать о массиве до первого цикла

Массив в 1С — упорядоченная коллекция значений любого типа. Индексы начинаются с нуля. В одном массиве могут лежать число, строка, ссылка на справочник и другой массив.

Методы, которые понадобятся при переборе:

МетодЧто делает
Количество()число элементов
ВГраница()индекс последнего элемента; у пустого массива равен -1
Добавить(Значение)добавляет в конец
Вставить(Индекс, Значение)вставляет в позицию, остальные сдвигаются
Удалить(Индекс)удаляет по индексу, хвост сдвигается влево
Найти(Значение)индекс первого совпадения или Неопределено
Получить(Индекс) / Установить(Индекс, Значение)то же, что Массив[Индекс]
Очистить()удаляет все элементы

Два свойства, которые влияют на выбор цикла:

  • 🔢 Новый Массив(5) создаёт массив из пяти элементов со значением Неопределено, а не пустой массив «на вырост».
  • 🔒 ФиксированныйМассив перебирается так же, но менять его нельзя. Такие массивы приходят, например, из параметров формы.

Перебор массива циклом «Для Каждого»

Самый короткий и читаемый вариант. Платформа сама проходит элементы от первого к последнему, счётчик не нужен.

Товары = Новый Массив;
Товары.Добавить("Стол");
Товары.Добавить("Стул");
Товары.Добавить("Шкаф");

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

Внутри цикла работают Прервать (выйти из цикла) и Продолжить (перейти к следующему элементу):

Для Каждого Сумма Из Суммы Цикл
    Если Сумма = 0 Тогда
        Продолжить;
    КонецЕсли;
    Если Сумма < 0 Тогда
        Прервать;
    КонецЕсли;
    Итого = Итого + Сумма;
КонецЦикла;

Ограничения «Для Каждого»

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

Перебор значений массива по индексу

Цикл Для … По нужен, когда важна позиция: заменить элемент, сравнить с соседним, вывести «1. Стол, 2. Стул».

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

Для пустого массива ВГраница() вернёт -1, и цикл не выполнится ни разу. Отдельная проверка на пустоту не нужна.

Нумерация для пользователя начинается с единицы, поэтому к индексу добавляют 1. Число со строкой склеивайте через Формат, иначе после 999 появится пробел-разделитель групп:

Для Индекс = 0 По Товары.ВГраница() Цикл
    Сообщить(Формат(Индекс + 1, "ЧГ=0") + ". " + Товары[Индекс]);
КонецЦикла;

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

Это главный приём, ради которого цикл по индексу и существует. При удалении хвост массива сдвигается влево. Если идти от начала, следующий элемент встанет на место удалённого и будет пропущен.

Идите с конца. Границу зафиксируйте в переменной до цикла:

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

Удаление элемента с индексом 7 не трогает элементы 0–6, поэтому оставшиеся шаги работают с корректными позициями.

Цикл «Пока»

Пока удобен, когда условие выхода зависит не только от длины массива: например, набираем документы, пока сумма не превысит лимит.

Индекс = 0;
Набрано = 0;
Пока Индекс <= Суммы.ВГраница() И Набрано < Лимит Цикл
    Набрано = Набрано + Суммы[Индекс];
    Индекс = Индекс + 1;
КонецЦикла;

Не забывайте увеличивать счётчик: без Индекс = Индекс + 1 цикл станет бесконечным и повесит сеанс.

Двумерный массив

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

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

Матрица[2][3] получит значение 23.

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

Ассоциативных массивов в привычном смысле в 1С два: Соответствие (ключ любого типа) и Структура (ключ — строка-идентификатор). Обход у обоих одинаковый: переменная цикла получает объект КлючИЗначение.

Остатки = Новый Соответствие;
Остатки.Вставить("Стол", 4);
Остатки.Вставить("Стул", 12);

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

Особенности:

  • 🔀 Порядок обхода Соответствия не гарантирован. Если важна последовательность, держите рядом массив ключей или используйте СписокЗначений.
  • 🔑 Чтобы получить значение, перебирать не нужно: Остатки["Стол"] или Остатки.Получить("Стол"). Для отсутствующего ключа вернётся Неопределено.
  • ✏️ Менять значение через Пара.Значение = 5 бесполезно. Пишите через сам объект: Остатки.Вставить(Пара.Ключ, 5). Удалять ключи во время обхода не стоит — сначала соберите их в массив, затем удалите отдельным циклом.

Структура перебирается тем же кодом, только ключ всегда строка. Для доступа к полю удобнее Параметры.Свойство("Склад", Склад) — метод вернёт Истина и запишет значение во вторую переменную.

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

В JavaScript или Python есть map, filter и reduce. Во встроенном языке 1С анонимных функций нет, и у массива нет методов, принимающих функцию. Аналоги делают двумя путями: своими функциями-обёртками и через ТаблицуЗначений.

Свои функции «Отобрать», «Преобразовать», «Свернуть»

Вынесите типовые операции в общий модуль. Код короткий, отлаживается обычным отладчиком и работает быстрее, чем любая «универсальная» конструкция.

// Фильтр: оставить положительные
Функция ТолькоПоложительные(Числа) Экспорт
    Результат = Новый Массив;
    Для Каждого Число Из Числа Цикл
        Если Число > 0 Тогда
            Результат.Добавить(Число);
        КонецЕсли;
    КонецЦикла;
    Возврат Результат;
КонецФункции

// Преобразование: применить скидку к каждой цене
Функция ЦеныСоСкидкой(Цены, Процент) Экспорт
    Результат = Новый Массив;
    Для Каждого Цена Из Цены Цикл
        Результат.Добавить(Окр(Цена * (100 - Процент) / 100, 2));
    КонецЦикла;
    Возврат Результат;
КонецФункции

// Свёртка: сумма элементов
Функция Сумма(Числа) Экспорт
    Итог = 0;
    Для Каждого Число Из Числа Цикл
        Итог = Итог + Число;
    КонецЦикла;
    Возврат Итог;
КонецФункции

Проверка: для массива с ценами 1000 и 250 и скидкой 10 % функция ЦеныСоСкидкой вернёт 900 и 225. Литералов массива вида [1, 2] во встроенном языке нет, массив собирают через Добавить.

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

Таблица значений вместо цепочки map/filter

Когда над набором нужно и отобрать, и сгруппировать, и посчитать итог, удобнее переложить данные в ТаблицуЗначений:

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Склад");
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
// … заполнение строк …

ТЗ.Свернуть("Склад", "Сумма");               // группировка с суммой
Всего   = ТЗ.Итог("Сумма");                  // свёртка
Строки  = ТЗ.НайтиСтроки(Новый Структура("Склад", "Основной")); // фильтр
Склады  = ТЗ.ВыгрузитьКолонку("Склад");      // обратно в массив

Готовые функции БСП и платформы

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

Платформенные функции строк тоже работают с массивами: СтрСоединить(Массив, ", ") склеивает элементы в строку, СтрРазделить(Строка, ",") делает из строки массив.

💡 Отдельного метода сортировки у массива нет. Загрузите значения в СписокЗначений методом ЗагрузитьЗначения, вызовите СортироватьПоЗначению() и выгрузите обратно через ВыгрузитьЗначения().

Как ускорить перебор больших массивов

Сам по себе цикл по 100 000 элементов выполняется быстро. Тормозит то, что вы делаете на каждом шаге.

Поиск внутри цикла

Массив.Найти() каждый раз просматривает массив с начала. Поиск внутри цикла по другому массиву даёт квадратичную сложность: 10 000 × 10 000 = 100 миллионов сравнений.

Решение — один раз переложить «справочный» массив в Соответствие и проверять по ключу:

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

Новые = Новый Массив;
Для Каждого Код Из КодыИзФайла Цикл
    Если Известные[Код] = Неопределено Тогда
        Новые.Добавить(Код);
    КонецЕсли;
КонецЦикла;

Поиск по ключу Соответствия не зависит от размера коллекции. Вместо 100 миллионов операций получается около 20 тысяч.

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

  • 🐢 Запрос внутри цикла — это сотни обращений к СУБД. Передайте весь массив параметром: Запрос.УстановитьПараметр("Номенклатура", Массив) и условие ГДЕ Ссылка В (&Номенклатура).
  • 🐢 Обращение через точку Товар.Ссылка.Владелец.Наименование читает объект из базы. Получите нужные реквизиты одним запросом заранее.
  • 🐢 На клиенте вызов серверной функции в цикле — это отдельный серверный вызов на каждый элемент. Передайте массив на сервер целиком и обработайте там одним вызовом.

Прочие приёмы

  • 📐 Если размер известен заранее, создайте Новый Массив(N) и заполняйте через Массив[Индекс] = … — без многократного Добавить.
  • ✂️ Вставить(0, Значение) и Удалить(0) сдвигают весь массив. В большом цикле они дорогие: собирайте новый массив через Добавить вместо удаления из старого.
  • ⏹️ Выходите через Прервать, как только нашли нужное.

Сравнение способов перебора: что выбрать

СпособКогда подходитОграничения
Для Каждогочитаем значения, меняем свойства объектов-элементовнет индекса, нельзя удалять и добавлять
Для … По от 0нужен номер, замена элементов, сравнение соседейпри удалении пропускает элементы
Для … По с концаудаление элементов по условиючуть менее очевиден в чтении
Покавыход по сложному условиюлегко забыть шаг счётчика
Обход Соответствияпары «ключ — значение»порядок не гарантирован
ТаблицаЗначенийгруппировка, итоги, отбор по нескольким полямлишний расход памяти на маленьких наборах
Запрос с параметром-массивомотбор данных из базы по спискутолько для данных в информационной базе
Проверьте себя
Нужно удалить из массива все пустые строки. Какой цикл выбрать?
Что вернёт ВГраница() у пустого массива?
Как быстрее проверить, есть ли код в массиве из 50 000 элементов, если проверок много?

Ошибки при переборе массивов

  • ❌ Выход за границу. Цикл Для Индекс = 0 По Массив.Количество() Цикл делает лишний шаг: последний индекс равен Количество() - 1. Используйте ВГраница().
  • ❌ Индекс с единицы. Привычка из других языков: Массив[1] — второй элемент, первый — Массив[0].
  • ❌ Сравнение с Неопределено после Найти. Найти() возвращает число (индекс, в том числе 0) или Неопределено, а не Истина/Ложь. Сравнивайте явно: Если Массив.Найти(Х) <> Неопределено Тогда.
  • ❌ Копия вместо ссылки. Копия = Массив не копирует данные: обе переменные указывают на один массив. Для копии создайте новый массив и заполните его циклом или через ДополнитьМассив.
  • ❌ Массив ссылок с клиента. Массив можно передать на сервер, но объекты (СправочникОбъект, ДокументОбъект) через границу клиент — сервер не передаются. Передавайте ссылки.

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

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