Обход СписокЗначений в 1С 8.3: циклы, индексы, поиск и удаление элементов
Содержание
Список значений в 1С 8.3 перебирают циклом Для Каждого Элемент Из Список Цикл ... КонецЦикла. На каждом шаге переменная Элемент содержит не само значение, а объект ЭлементСпискаЗначений, поэтому нужное значение берут через Элемент.Значение, а подпись — через Элемент.Представление.
Если нужен номер позиции, соседний элемент или удаление по ходу обхода, перебирают по индексу: Для Индекс = 0 По Список.Количество() - 1 Цикл. Индексы в списке значений начинаются с нуля. Удалять элементы безопасно только в обратном цикле, от последнего к первому.
Из чего состоит СписокЗначений
СписокЗначений — упорядоченная коллекция. Каждый её элемент — отдельный объект типа ЭлементСпискаЗначений с четырьмя свойствами:
| Свойство | Что хранит | Пример |
|---|---|---|
Значение | Само значение любого типа | ссылка на контрагента, число, строка |
Представление | Текст, который видит пользователь | «ООО Ромашка (основной)» |
Пометка | Булево, флажок у строки | Истина |
Картинка | Картинка у строки в форме | значок из библиотеки |
Заполняют список методом Добавить(). Параметры идут в том же порядке, что в таблице:
Список = Новый СписокЗначений;
Список.Добавить(100, "Сто");
Список.Добавить(250, "Двести пятьдесят", Истина);
Список.Добавить(40); // представление пустое, в форме покажется само значение
Ещё несколько методов, которые понадобятся при обходе:
- 🔢
Количество()— число элементов; - 📍
Получить(Индекс)илиСписок[Индекс]— элемент по номеру; - 🔎
Индекс(Элемент)— номер элемента в списке; - ➕
Вставить(Индекс, Значение, Представление)— вставка в нужную позицию; - ➖
Удалить(Индекс)илиУдалить(Элемент)— удаление; - 📦
ВыгрузитьЗначения()иЗагрузитьЗначения(Массив)— обмен с массивом.
У самого списка есть свойство ТипЗначения (описание типов). Если его задать, список проверяет тип добавляемых значений. В коде его обычно не трогают, а в формах им ограничивают ввод.
Перебор списка значений циклом Для Каждого
Это основной и самый читаемый способ. Платформа сама идёт от первого элемента к последнему, счётчик заводить не нужно.
Итого = 0;
Для Каждого Элемент Из Список Цикл
Итого = Итого + Элемент.Значение;
Сообщить(Элемент.Представление + ": " + Формат(Элемент.Значение, "ЧГ=0"));
КонецЦикла;
Сообщить("Итого: " + Формат(Итого, "ЧГ=0"));
Для списка из примера выше в Итого окажется 390 (100 + 250 + 40).
Внутри цикла работают два оператора управления:
- ⏭️
Продолжить— пропустить текущий элемент и перейти к следующему; - ⛔
Прервать— выйти из цикла совсем, например когда нужный элемент уже найден.
Для Каждого Элемент Из Список Цикл
Если Не Элемент.Пометка Тогда
Продолжить;
КонецЕсли;
ОбработатьОтмеченный(Элемент.Значение);
КонецЦикла;
ЭлементСпискаЗначений — объект, а не копия. Если внутри цикла присвоить Элемент.Значение = Элемент.Значение * 2, изменится сам список. Это удобно для массовой правки: пометить все строки, поправить представления, пересчитать числа.
Для Каждого Элемент Из Список Цикл
Элемент.Пометка = Истина;
Элемент.Представление = "№ " + Формат(Список.Индекс(Элемент) + 1, "ЧГ=0") + " — " + Строка(Элемент.Значение);
КонецЦикла;
Для простой отметки всех строк есть готовый метод: Список.ЗаполнитьПометки(Истина). Цикл для этого писать не нужно.
Обход списка значений по индексу
Цикл со счётчиком нужен, когда важна позиция: вывести номер строки, сравнить элемент с предыдущим, обработать каждый второй. Первый элемент имеет индекс 0, последний — Количество() - 1.
Для Индекс = 0 По Список.Количество() - 1 Цикл
Элемент = Список[Индекс];
Сообщить(Формат(Индекс, "ЧН=0; ЧГ=0") + ". " + Строка(Элемент.Значение));
КонецЦикла;
Формат ЧН=0 нужен, чтобы ноль вывелся цифрой, а не пустой строкой.
Граница Список.Количество() - 1 вычисляется один раз, перед первым шагом. Если список пустой, выражение даёт −1, и цикл не выполнится ни разу — ошибки не будет.
Сравнение соседних элементов
Пример: найти, где числовой ряд перестаёт расти.
Для Индекс = 1 По Список.Количество() - 1 Цикл
Текущее = Список[Индекс].Значение;
Предыдущее = Список[Индекс - 1].Значение;
Если Текущее < Предыдущее Тогда
Сообщить("Спад на позиции " + Формат(Индекс, "ЧГ=0"));
КонецЕсли;
КонецЦикла;
Цикл стартует с 1, чтобы у первого обрабатываемого элемента был предшественник с индексом 0.
Обратный цикл: удаление элементов при переборе
Удалять элементы внутри Для Каждого нельзя: после удаления остальные сдвигаются на одну позицию, и следующий элемент пропускается. Прямой цикл по индексу ломается так же. Рабочий приём — идти с конца:
Всего = Список.Количество();
Для Сч = 1 По Всего Цикл
Индекс = Всего - Сч;
Если Список[Индекс].Значение = 0 Тогда
Список.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
При Всего = 5 индекс пройдёт значения 4, 3, 2, 1, 0. Удаление элемента с индексом 3 не трогает позиции 0–2, которые ещё впереди, поэтому ничего не теряется.
Не удаляйте элементы в цикле Для Каждого — список «проскочит» часть элементов, и ошибку заметят только по неверному результату.
Цикл Пока
Цикл Пока уместен, если число шагов заранее неизвестно: например, удаляете элементы, пока список не уложится в лимит.
Пока Список.Количество() > 10 Цикл
Список.Удалить(Список.Количество() - 1);
КонецЦикла;
Здесь условие пересчитывается на каждом шаге — это и нужно, ведь список меняется.
Какой цикл быстрее и когда это важно
На списках в сотни и тысячи элементов разница между циклами не видна: время уходит на то, что происходит внутри, а не на сам перебор. Выбирайте способ по задаче, а не по скорости.
| Способ | Когда выбирать | Риски |
|---|---|---|
Для Каждого ... Из | Обычный проход по всем элементам | Нет индекса; нельзя удалять в процессе |
Для Индекс = 0 По ... | Нужна позиция, соседи, шаг через один | Ошибка на единицу, если начать с 1 |
Обратный Для Сч = 1 По Всего | Удаление элементов по условию | Нужно аккуратно считать индекс |
Пока ... Цикл | Число шагов зависит от изменений списка | Вечный цикл, если условие не меняется |
ВыгрузитьЗначения() + массив | Нужны только значения, без представлений | Пометки и представления теряются |
Где реально теряется время:
- 🐢 вызов сервера на каждой итерации в клиентском коде — собирайте данные одним вызовом до цикла;
- 🐢 запрос к базе внутри цикла по ссылкам — одна выборка с параметром-списком быстрее сотни мелких;
- 🐢
НайтиПоЗначению()внутри цикла по другому большому списку — поиск линейный, общая работа растёт как произведение размеров; - 🐢 обращение через точку к реквизитам ссылки (
Элемент.Значение.ИНН) — каждый такой доступ читает объект из базы.
Если сомневаетесь, замерьте. Для этого есть функция ТекущаяУниверсальнаяДатаВМиллисекундах():
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Каждого Элемент Из Список Цикл
// обработка
КонецЦикла;
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт;
Сообщить("Цикл занял, мс: " + Формат(Длительность, "ЧН=0; ЧГ=0"));
Для точной картины используйте замер производительности в отладчике Конфигуратора или EDT: он показывает время по каждой строке кода.
Список значений можно передать параметром в запрос: ГДЕ Номенклатура.Ссылка В (&СписокТоваров). Платформа сама возьмёт значения элементов. Это быстрее, чем перебирать ссылки и обращаться к каждой отдельно.
Поиск и отбор элементов в списке значений
Поиск по значению
Метод НайтиПоЗначению() возвращает первый элемент с таким значением или Неопределено, если ничего не нашёл:
Найденный = Список.НайтиПоЗначению(250);
Если Найденный = Неопределено Тогда
Сообщить("Значения 250 в списке нет");
Иначе
Сообщить("Нашли на позиции " + Формат(Список.Индекс(Найденный), "ЧН=0; ЧГ=0"));
КонецЕсли;
Проверка на Неопределено обязательна. Без неё следующая строка с Найденный.Представление упадёт с ошибкой обращения к полю.
Поиск по представлению или условию
Готового поиска по представлению у списка нет. Его пишут циклом с Прервать:
Результат = Неопределено;
Для Каждого Элемент Из Список Цикл
Если СтрНайти(НРег(Элемент.Представление), "двести") > 0 Тогда
Результат = Элемент;
Прервать;
КонецЕсли;
КонецЦикла;
Фильтр: новый список по условию
Метода «отобрать» у списка значений тоже нет. Удобнее всего собрать новый список и не трогать исходный:
Отобранные = Новый СписокЗначений;
Для Каждого Элемент Из Список Цикл
Если Элемент.Значение > 50 Тогда
Отобранные.Добавить(Элемент.Значение, Элемент.Представление, Элемент.Пометка);
КонецЕсли;
КонецЦикла;
Для списка 100, 250, 40 в Отобранные попадут два элемента: 100 и 250.
Частый сценарий в формах — работать только с отмеченными строками. Пользователь ставит флажки, код берёт помеченные элементы тем же фильтром по Элемент.Пометка.
Много поисков подряд
Если нужно сотни раз проверять «есть ли значение в списке», один раз переложите значения в Соответствие и ищите по ключу:
Индекс = Новый Соответствие;
Для Каждого Элемент Из Список Цикл
Индекс.Вставить(Элемент.Значение, Элемент);
КонецЦикла;
ЕстьВСписке = (Индекс.Получить(250) <> Неопределено);
Поиск по ключу в соответствии не перебирает все элементы, поэтому на больших объёмах выигрыш заметный. Учтите: при повторяющихся значениях в соответствии останется последний элемент.
Перебор вместе с сортировкой и выгрузкой в массив
Часто перед обходом список нужно упорядочить. Для этого есть два метода:
СортироватьПоЗначению(НаправлениеСортировки.Возр);СортироватьПоПредставлению(НаправлениеСортировки.Убыв).
Оба меняют порядок в самом списке. Если исходный порядок ещё понадобится, сначала сделайте копию методом Скопировать() и сортируйте её.
Когда представления и пометки не нужны, проще работать с массивом:
Значения = Список.ВыгрузитьЗначения();
Для Каждого Значение Из Значения Цикл
Сообщить(Значение);
КонецЦикла;
Здесь переменная цикла — уже само значение, без .Значение. Обратная операция — Список.ЗагрузитьЗначения(Массив): она очищает список и заполняет его элементами массива с пустыми представлениями.
Три типовые задачи на перебор списка значений
Собрать значения в строку через запятую
Нужно для сообщений и логов: «Выбраны: Москва, Казань, Пермь». Если значения — строки, хватит одной строки кода: СтрСоединить(Список.ВыгрузитьЗначения(), ", "). Для чисел и ссылок соберите массив представлений циклом:
Части = Новый Массив;
Для Каждого Элемент Из Список Цикл
Части.Добавить(Строка(Элемент.Значение));
КонецЦикла;
Текст = СтрСоединить(Части, ", ");
Убрать повторяющиеся значения
Обратный цикл плюс соответствие с уже встреченными значениями. Удаляем более ранний дубль, поэтому в списке остаётся последнее вхождение каждого значения:
Встречено = Новый Соответствие;
Всего = Список.Количество();
Для Сч = 1 По Всего Цикл
Индекс = Всего - Сч;
Значение = Список[Индекс].Значение;
Если Встречено.Получить(Значение) <> Неопределено Тогда
Список.Удалить(Индекс);
Иначе
Встречено.Вставить(Значение, Истина);
КонецЕсли;
КонецЦикла;
Для ряда 5, 7, 5, 9, 7 останется 5, 9, 7: цикл идёт с конца, встречает 7 и 9, затем 5, а повторные 7 и 5 в начале удаляет.
Посчитать сумму только отмеченных строк
Пользователь отмечает флажками позиции в форме, а код считает итог по выбранным:
Сумма = 0;
Отмечено = 0;
Для Каждого Элемент Из Список Цикл
Если Элемент.Пометка Тогда
Сумма = Сумма + Элемент.Значение;
Отмечено = Отмечено + 1;
КонецЕсли;
КонецЦикла;
Если в списке 100 (отмечен), 250 (отмечен) и 40 (без отметки), получится Сумма = 350 и Отмечено = 2.
Ошибки при переборе списка значений и как их избежать
Ещё две ошибки встречаются реже, но отнимают много времени:
- ⚠️ Изменения «не видны» в форме. Если в форме список хранится в реквизите, а код меняет его копию, полученную через
Скопировать(), исходный реквизит остаётся прежним. Меняйте сам реквизит формы. - ⚠️ Диалоговые методы на сервере.
ПоказатьВыборЭлемента()иПоказатьОтметкуЭлементов()работают только на клиенте. В серверной процедуре их вызвать нельзя, а сам перебор и поиск работают и там, и там.
Частые вопросы
Можно ли перебрать список значений в обратном порядке без индекса?
Для Сч = 1 По Всего Цикл и индекс Всего - Сч.Чем список значений отличается от массива при переборе?
Как узнать номер текущего элемента в цикле Для Каждого?
Список.Индекс(Элемент). Если номер нужен на каждом шаге, проще сразу писать цикл по индексу.