Как правильно удалять строки из таблицы значений в 1С в цикле без ошибок индексации?

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

При работе с коллекциями в платформе «1С:Предприятие» начинающие разработчики часто сталкиваются с крайне неприятной ошибкой: «Индекс находится за пределами границы массива». Эта проблема возникает при попытке удалить строки из таблицы значений (или элементы из массива) непосредственно внутри обходящего её цикла «от 0 до N».

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

Код, который изначально пытался использовать автор темы, выглядит следующим образом:


КоличествоСтрокФин = тпБюджетныеОперации.Количество() - 1;
Для НС = 0 по КоличествоСтрокФин - 1 Цикл
    Для НС1 = 1 по КоличествоСтрокФин Цикл
        Если тпБюджетныеОперации[НС][1] = тпБюджетныеОперации[НС1][1] 
           И тпБюджетныеОперации[НС][2] = тпБюджетныеОперации[НС1][2] Тогда 
            тпБюджетныеОперации.Удалить(НС1);
            КоличествоСтрокФин = тпБюджетныеОперации.Количество() - 1;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Давайте выясним причину, по которой этот код неработоспособен и приводит к аварийному завершению программы.


Почему возникает ошибка выхода за пределы индекса?

Разберем механику работы прямого цикла по шагам. Представим, что в нашей таблице значений тпБюджетныеОперации изначально содержится 5 строк. Их индексы: 0, 1, 2, 3, 4. Переменная КоличествоСтрокФин перед началом выполнения цикла принимает значение 4.

Когда цикл доходит, например, до индекса 1 и принимает решение удалить строку с этим индексом, на уровне платформы происходит следующее:

  1. Строка с индексом 1 физически удаляется из таблицы.
  2. Все последующие строки автоматически сдвигаются на одну позицию «наверх» (к началу таблицы). То есть бывшая строка 2 получает индекс 1, строка 3 — индекс 2, а строка 4 — индекс 3.
  3. Общее фактическое количество строк в таблице становится равным 4 (максимальный доступный индекс теперь равен 3).
  4. Переменная счетчика внешнего цикла при этом неумолимо переходит к следующему шагу — к индексу 2.

В результате этого процесса возникают сразу две критические проблемы:

Проблема №1: Пропуск данных при проверке. Так как строка, находившаяся на позиции 2, сместилась на позицию 1, а счетчик перешел на индекс 2, мы полностью пропустили анализ сместившейся строки. Она никогда не будет проверена алгоритмом.

Проблема №2: Выход за границы коллекции. Граница цикла была жестко зафиксирована в самом начале (в переменной КоличествоСтрокФин). Даже если автор пытается переприсвоить её значение внутри цикла, в классическом цикле Для ... По ... платформа 1С вычисляет верхнюю границу один раз при входе в цикл. Изменение переменной внутри тела цикла никак не влияет на количество итераций. Когда счетчик дойдет до исходного верхнего предела (например, до 4), физически этой строки в таблице уже не будет существовать. Программа мгновенно остановит работу с ошибкой индексации.

Также стоит отметить крайне неэффективную вычислительную сложность исходного алгоритма — O(N²). При наличии 10 000 строк система выполнит около 100 000 000 операций сравнения, что приведет к глухому зависанию сеанса пользователя.

Рассмотрим правильные и эффективные альтернативные варианты решения этой задачи.


Способ 1. Безопасное удаление строк через массив (без использования числовых индексов)

Один из самых простых и логичных способов, рекомендованных разработчиками на форуме, заключается в разделении процесса на два этапа: сначала мы находим элементы, подлежащие удалению, а затем удаляем их.

Многие программисты забывают, что метод таблицы значений Удалить() может принимать в качестве параметра не только целочисленный индекс, но и **прямую ссылку на сам объект строки** (СтрокаТаблицыЗначений). При удалении строки по объекту нам абсолютно не важно, как сдвинулись числовые индексы в процессе очистки.

Посмотрим на пример реализации данного подхода:


// Шаг 1: Создаем массив для накопления строк, предназначенных для удаления
МассивДляУдаления = Новый Массив;

// Шаг 2: Проходим по таблице циклом "Для Каждого" и определяем дубли
Для Каждого Строка1 Из тпБюджетныеОперации Цикл
    // Для демонстрации используем условный поиск дубликатов. 
    // Реальный быстрый алгоритм поиска дублей мы разберем в Способе 3.
    Если Строка1.Колонка1 = "Дубликат" Тогда
        МассивДляУдаления.Добавить(Строка1);
    КонецЕсли;
КонецЦикла;

// Шаг 3: Безопасно удаляем накопленные строки без привязки к числовым индексам
Для Каждого СтрокаДляУдаления Из МассивДляУдаления Цикл
    тпБюджетныеОперации.Удалить(СтрокаДляУдаления);
КонецЦикла;

Этот способ невероятно надежен, прост в чтении кода и полностью исключает вероятность возникновения ошибок переполнения или выхода за границы массива.


Способ 2. Обратный цикл (Обход коллекции с конца к началу)

Если вам по архитектурным соображениям или требованиям алгоритма обязательно нужно использовать числовые индексы элементов, то обходить таблицу необходимо строго **в обратном порядке** — от самой последней строки к первой.

Почему это работает? Когда мы удаляем строку с конца таблицы значений, сдвиг элементов происходит только «после» текущей позиции (в той области, которую мы уже успешно проверили и отработали). Индексы строк, которые нам только предстоит проверить (находящиеся ближе к началу таблицы), остаются неизменными.

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

Вариант А. Через стандартный цикл "Пока" с уменьшением счетчика:


Индекс = тпБюджетныеОперации.Количество() - 1;

Пока Индекс >= 0 Цикл
    СтрокаТекущая = тпБюджетныеОперации[Индекс];
    
    // Проверяем условие для удаления
    Если СтрокаТекущая.Колонка1 = "Удалить" Тогда
        тпБюджетныеОперации.Удалить(Индекс);
    КонецЕсли;
    
    // Шаг назад совершаем обязательно на каждой итерации
    Индекс = Индекс - 1; 
КонецЦикла;

Вариант Б. Математический трюк с циклом "Для":


// Переводим индексы в отрицательный диапазон для эмуляции обратного шага
Для ОтрицательныйИндекс = -(тпБюджетныеОперации.Количество() - 1) По 0 Цикл
    ФактическийИндекс = -ОтрицательныйИндекс;
    СтрокаТекущая = тпБюджетныеОперации[ФактическийИндекс];
    
    Если СтрокаТекущая.Колонка1 = "Удалить" Тогда
        тпБюджетныеОперации.Удалить(ФактическийИндекс);
    КонецЕсли;
КонецЦикла;

Оба варианта работают корректно, однако вариант с циклом Пока считается более интуитивным и легким для понимания другими разработчиками при последующем сопровождении программного кода.


Способ 3. Оптимальный поиск дублей через "Соответствие" за один проход

Вернемся к бизнес-задаче автора: удаление дублирующихся строк по совпадению значений в определенных колонках. Вложенные циклы крайне медлительны. Проанализируем, как можно решить эту задачу профессионально, используя коллекцию Соответствие.

Объект Соответствие работает по принципу хэш-таблицы и обеспечивает мгновенный доступ к значениям по ключу со сложностью **O(1)**. Мы можем обойти таблицу значений всего один раз, собирая уникальные ключи строк. Если ключ уже присутствует в соответствии — перед нами дубликат, который мы сразу отправляем в массив на удаление.

Рассмотрим пример кода, который решает задачу автора темы максимально быстро:


УникальныеКлючи = Новый Соответствие;
МассивДляУдаления = Новый Массив;

Для Каждого Строка Из тпБюджетныеОперации Цикл
    // Формируем составной уникальный ключ из проверяемых колонок (например, индексы 1 и 2)
    КлючПроверки = Строка[1] + "|||" + Строка[2];
    
    Если УникальныеКлючи[КлючПроверки] <> Неопределено Тогда
        // Если ключ уже есть в Соответствии, значит, это дублирующая строка
        МассивДляУдаления.Добавить(Строка);
    Иначе
        // Если ключ встретился впервые, фиксируем его уникальность
        УникальныеКлючи.Вставить(КлючПроверки, Истина);
    КонецЕсли;
КонецЦикла;

// Удаляем собранные дубликаты
Для Каждого СтрокаДляУдаления Из МассивДляУдаления Цикл
    тпБюджетныеОперации.Удалить(СтрокаДляУдаления);
КонецЦикла;

Данный алгоритм работает со сложностью **O(N)**. На практике это означает, что таблица из 100 000 строк обработается за доли секунды вместо нескольких часов ожидания при использовании вложенных циклов.


Способ 4. Платформенный метод "Свернуть"

В сообщении №10 автору справедливо предложили самый простой и быстрый инструмент, встроенный в платформу «1С:Предприятие» — метод Свернуть(). Он предназначен специально для группировки строк и удаления дубликатов.

Если перед нами стоит задача оставить в таблице только уникальные комбинации значений определенных колонок, мы можем сделать это буквально одной строкой:


// Первым параметром перечисляем имена колонок для группировки,
// вторым — имена колонок, показатели которых нужно просуммировать.
тпБюджетныеОперации.Свернуть("ИмяКолонки1, ИмяКолонки2", "");

Важные особенности использования метода Свернуть():


Подведем итоги

Мы проанализировали причины возникновения ошибок индексации и рассмотрели эффективные паттерны программирования в 1С:

  1. Никогда не удаляйте строки внутри прямого цикла по индексам (Для Индекс = 0 По КоличествоСтрок).
  2. Используйте **обратный обход** таблицы значений с конца к началу, если критически важно выполнять удаление по индексам.
  3. Применяйте **накопление удаляемых объектов в массив** с их последующим удалением — это самый безопасный способ при обходе таблиц циклом Для Каждого.
  4. Для высокопроизводительного поиска дубликатов на больших объемах данных используйте хэширование через объект Соответствие или встроенный метод Свернуть().
← На главную