Как правильно удалять элементы из массива по условию и работать с диапазонами чисел в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработчика 1С часто возникают задачи, связанные с обработкой коллекций данных. Одной из базовых, но коварных операций является удаление элементов из массива. Ошибка новичка заключается в непонимании того, как меняются индексы коллекции при удалении элементов в прямом цикле. Рассмотрим на конкретном примере задачу: заполнить массив числами от 1 до 100 и удалить каждый нечетный десяток (числа 1–10, 21–30, 41–50 и т.д.).

Проблема прямого обхода и смещения индексов

Разберем типичную ситуацию. Если мы используем стандартный цикл Для каждого или обычный цикл Для Индекс = 0 По Массив.ВГраница(), то при выполнении метода Массив.Удалить(Индекс) все последующие элементы сдвигаются на одну позицию влево. В результате итератор цикла "перепрыгивает" через один элемент, а в конце цикла программа выдает ошибку "Значение индекса выходит за границы диапазона", так как общее количество элементов уменьшилось, а цикл пытается дойти до старого значения границы.

Проанализируем "топорный" метод, с которого начал автор темы. Ручное удаление каждого индекса (89, 88, 87...) технически работает, потому что удаление идет с конца, но такой код невозможно поддерживать или масштабировать. Если элементов будет не 100, а 1000, ручной ввод станет невозможен.

Решение №1: Обход массива в обратном порядке (Классический метод)

Наиболее надежный и эффективный способ удаления элементов из любой индексированной коллекции в 1С (Массив, ТаблицаЗначений, СписокЗначений) — это обход с конца. Когда мы удаляем последний или предпоследний элемент, индексы тех элементов, которые мы еще не успели обработать (тех, что стоят в начале), остаются неизменными.

Рассмотрим реализацию этого алгоритма с использованием цикла Пока:


&НаКлиенте
Процедура УдалитьНечетныеДесятки()
    // 1. Заполняем массив
    МассивЧисел = Новый Массив;
    Для Число = 1 По 100 Цикл
        МассивЧисел.Добавить(Число);
    КонецЦикла;

    // 2. Удаляем элементы в обратном порядке
    Индекс = МассивЧисел.Количество() - 1; 
    Пока Индекс >= 0 Цикл
        ТекущееЗначение = МассивЧисел[Индекс];
        
        // Определяем номер десятка. 
        // Для чисел 1-10 это будет 0 (первый десяток)
        // Для чисел 11-20 это будет 1 (второй десяток)
        НомерДесятка = Цел((ТекущееЗначение - 1) / 10);
        
        // Если остаток от деления на 2 равен 0, значит десяток нечетный (1, 3, 5...)
        Если НомерДесятка % 2 = 0 Тогда
            МассивЧисел.Удалить(Индекс);
        КонецЕсли;
        
        Индекс = Индекс - 1;
    КонецЦикла;

    // 3. Вывод результата
    Для Каждого Элемент Из МассивЧисел Цикл
        Сообщить(Элемент);
    КонецЦикла;
КонецПроцедуры

Решение №2: Использование цикла "Для" с отрицательным шагом

В языке 1С синтаксис цикла Для не позволяет напрямую задать отрицательный шаг (как Step -1 в других языках). Однако программисты часто используют математическую хитрость с переворотом индекса. Проанализируем, как это реализовано в обсуждении:


ПоследнийИндекс = МассивЧисел.ВГраница();
Для Счетчик = -ПоследнийИндекс По 0 Цикл
    Индекс = -Счетчик; // Получаем положительный индекс от ВГраница() до 0
    Если (МассивЧисел[Индекс] - 1) % 20 < 10 Тогда
        МассивЧисел.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

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

Решение №3: Математическая оптимизация условий

Выясним причину использования формулы (Значение - 1) % 20 < 10. Это изящный способ определить, попадает ли число в нечетный десяток, не прибегая к функции Цел() (целочисленное деление):

  1. Для чисел 1–10: (1-1..10-1) % 20 дает результат от 0 до 9. Условие < 10 истинно.
  2. Для чисел 11–20: (11-1..20-1) % 20 дает результат от 10 до 19. Условие < 10 ложно.
  3. Для чисел 21–30: Остаток снова возвращается в диапазон 0–9.

Такой подход работает быстрее, так как использует только базовую арифметику остатка от деления.

Почему создание нового массива может быть лучше?

Хотя в теме обсуждалось именно удаление, стоит рассмотреть ситуацию со стороны производительности. В 1С метод Удалить() для массива вынуждает платформу копировать (сдвигать) все элементы, находящиеся справа от удаляемого. Если массив содержит сотни тысяч записей, а удаляется каждая вторая, это приведет к колоссальным затратам ресурсов (сложность алгоритма O(n²)).

В реальных задачах мы рекомендуем использовать метод фильтрации в новый массив:


НовыйМассив = Новый Массив;
Для Каждого Элемент Из ИсходныйМассив Цикл
    Если НЕ (УсловиеУдаления) Тогда
        НовыйМассив.Добавить(Элемент);
    КонецЕсли;
КонецЦикла;
ИсходныйМассив = НовыйМассив;

Этот способ работает за линейное время O(n), так как операция Добавить() в конец массива значительно дешевле, чем Удалить() из середины.

Терминологическая точность: Числа и Цифры

В ходе дискуссии на форуме справедливо отметили важный момент для разработки документации и ТЗ: цифр существует всего десять (от 0 до 9 в десятичной системе). Все, что больше 9 — это числа, состоящие из цифр. При общении с заказчиком или написании комментариев к коду старайтесь использовать термин Число или Значение, чтобы избежать двусмысленности.

Резюме и рекомендации

Подведем итоги нашего анализа:

← На главную