В практике разработчика 1С часто возникают задачи, связанные с обработкой коллекций данных. Одной из базовых, но коварных операций является удаление элементов из массива. Ошибка новичка заключается в непонимании того, как меняются индексы коллекции при удалении элементов в прямом цикле. Рассмотрим на конкретном примере задачу: заполнить массив числами от 1 до 100 и удалить каждый нечетный десяток (числа 1–10, 21–30, 41–50 и т.д.).
Разберем типичную ситуацию. Если мы используем стандартный цикл Для каждого или обычный цикл Для Индекс = 0 По Массив.ВГраница(), то при выполнении метода Массив.Удалить(Индекс) все последующие элементы сдвигаются на одну позицию влево. В результате итератор цикла "перепрыгивает" через один элемент, а в конце цикла программа выдает ошибку "Значение индекса выходит за границы диапазона", так как общее количество элементов уменьшилось, а цикл пытается дойти до старого значения границы.
Проанализируем "топорный" метод, с которого начал автор темы. Ручное удаление каждого индекса (89, 88, 87...) технически работает, потому что удаление идет с конца, но такой код невозможно поддерживать или масштабировать. Если элементов будет не 100, а 1000, ручной ввод станет невозможен.
Наиболее надежный и эффективный способ удаления элементов из любой индексированной коллекции в 1С (Массив, ТаблицаЗначений, СписокЗначений) — это обход с конца. Когда мы удаляем последний или предпоследний элемент, индексы тех элементов, которые мы еще не успели обработать (тех, что стоят в начале), остаются неизменными.
Рассмотрим реализацию этого алгоритма с использованием цикла Пока:
&НаКлиенте
Процедура УдалитьНечетныеДесятки()
// 1. Заполняем массив
МассивЧисел = Новый Массив;
Для Число = 1 По 100 Цикл
МассивЧисел.Добавить(Число);
КонецЦикла;
// 2. Удаляем элементы в обратном порядке
Индекс = МассивЧисел.Количество() - 1;
Пока Индекс >= 0 Цикл
ТекущееЗначение = МассивЧисел[Индекс];
// Определяем номер десятка.
// Для чисел 1-10 это будет 0 (первый десяток)
// Для чисел 11-20 это будет 1 (второй десяток)
НомерДесятка = Цел((ТекущееЗначение - 1) / 10);
// Если остаток от деления на 2 равен 0, значит десяток нечетный (1, 3, 5...)
Если НомерДесятка % 2 = 0 Тогда
МассивЧисел.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
// 3. Вывод результата
Для Каждого Элемент Из МассивЧисел Цикл
Сообщить(Элемент);
КонецЦикла;
КонецПроцедуры
В языке 1С синтаксис цикла Для не позволяет напрямую задать отрицательный шаг (как Step -1 в других языках). Однако программисты часто используют математическую хитрость с переворотом индекса. Проанализируем, как это реализовано в обсуждении:
ПоследнийИндекс = МассивЧисел.ВГраница();
Для Счетчик = -ПоследнийИндекс По 0 Цикл
Индекс = -Счетчик; // Получаем положительный индекс от ВГраница() до 0
Если (МассивЧисел[Индекс] - 1) % 20 < 10 Тогда
МассивЧисел.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Важный нюанс платформы: В цикле Для ... По граничные значения вычисляются один раз при входе в цикл. Это позволяет безопасно использовать такой подход для удаления, если мы идем "справа налево".
Выясним причину использования формулы (Значение - 1) % 20 < 10. Это изящный способ определить, попадает ли число в нечетный десяток, не прибегая к функции Цел() (целочисленное деление):
(1-1..10-1) % 20 дает результат от 0 до 9. Условие < 10 истинно.(11-1..20-1) % 20 дает результат от 10 до 19. Условие < 10 ложно.Такой подход работает быстрее, так как использует только базовую арифметику остатка от деления.
Хотя в теме обсуждалось именно удаление, стоит рассмотреть ситуацию со стороны производительности. В 1С метод Удалить() для массива вынуждает платформу копировать (сдвигать) все элементы, находящиеся справа от удаляемого. Если массив содержит сотни тысяч записей, а удаляется каждая вторая, это приведет к колоссальным затратам ресурсов (сложность алгоритма O(n²)).
В реальных задачах мы рекомендуем использовать метод фильтрации в новый массив:
НовыйМассив = Новый Массив;
Для Каждого Элемент Из ИсходныйМассив Цикл
Если НЕ (УсловиеУдаления) Тогда
НовыйМассив.Добавить(Элемент);
КонецЕсли;
КонецЦикла;
ИсходныйМассив = НовыйМассив;
Этот способ работает за линейное время O(n), так как операция Добавить() в конец массива значительно дешевле, чем Удалить() из середины.
В ходе дискуссии на форуме справедливо отметили важный момент для разработки документации и ТЗ: цифр существует всего десять (от 0 до 9 в десятичной системе). Все, что больше 9 — это числа, состоящие из цифр. При общении с заказчиком или написании комментариев к коду старайтесь использовать термин Число или Значение, чтобы избежать двусмысленности.
Подведем итоги нашего анализа:
ВГраница() для получения индекса последнего элемента.Цел() и остаток от деления %.Для каждого, так как это ведет к непредсказуемым результатам и пропуску элементов.