При работе с объектом ТаблицаЗначений начинающие разработчики часто сталкиваются с ошибкой "Значение индекса выходит за границы диапазона". Эта ситуация возникает в тот момент, когда мы пытаемся удалить строки в обычном цикле Для, перебирая их по возрастанию индекса. В данной статье мы детально разберем, почему это происходит, и изучим наиболее эффективные алгоритмы очистки таблиц, включая методы, подходящие для высоконагруженных систем.
Рассмотрим стандартную ситуацию. У нас есть ТаблицаЗначений, в которой 10 строк (индексы от 0 до 9). Мы запускаем цикл и удаляем строку с индексом 2. В этот же момент платформа 1С пересчитывает структуру таблицы: строка, которая была третьей (индекс 3), теперь становится второй (индекс 2). Все последующие строки также сдвигаются «вверх».
Если наш алгоритм продолжает работу и пытается обратиться к строке с индексом 9 (которая изначально была в таблице), он ее не находит, так как после удаления хотя бы одной строки общее количество элементов уменьшилось, и максимальный индекс стал равен 8. Именно этот механизм «сдвига» и приводит к фатальной ошибке. Выясним причину на примере исходного кода из обсуждения:
// Ошибочный подход
НомераСтрокДляУдаления = ПоискДублейСтрокВТабЧасти(СписокДокументов, Ложь);
Для каждого СтрокаСпискаДокументов из СписокДокументов цикл
Для каждого Элемент из НомераСтрокДляУдаления цикл
СписокДокументов.Удалить(Элемент.Значение); // Здесь возникнет ошибка при сдвиге индексов
КонецЦикла;
КонецЦикла;
Проанализируем самый простой и надежный способ — обход таблицы в обратном порядке (от большего индекса к меньшему). Разберем по шагам: если мы удаляем последнюю строку таблицы, индексы всех предшествующих строк остаются неизменными. Это позволяет нам безопасно пройти до самого начала таблицы (до индекса 0), не опасаясь смещения данных.
Для реализации этого метода нам понадобится цикл Для с указанием шага или ручным управлением счетчиком. Посмотрим на пример кода:
ВсегоСтрок = СписокДокументов.Количество();
// Начинаем с последнего индекса (Количество - 1) до нуля
Для Счетчик = 1 По ВсегоСтрок Цикл
ТекущийИндекс = ВсегоСтрок - Счетчик;
СтрокаТЗ = СписокДокументов[ТекущийИндекс];
// Проверяем условие удаления
Если НужноУдалитьСтроку(СтрокаТЗ) Тогда
СписокДокументов.Удалить(ТекущийИндекс);
КонецЕсли;
КонецЦикла;
Важный нюанс: при использовании метода Удалить() по индексу, мы всегда должны быть уверены, что работаем в обратном порядке. Это «золотое правило» разработки в 1С при манипуляциях с любыми коллекциями (массивами, таблицами значений, частями документов).
Рассмотрим альтернативный вариант, который позволяет избежать путаницы с индексами. Метод Удалить() у объекта ТаблицаЗначений может принимать в качестве параметра не только число (индекс), но и саму СтрокаТаблицыЗначений (объект). Когда мы удаляем по объекту, платформе не важно, какой сейчас индекс у строки, она находит конкретный объект в памяти и исключает его из коллекции.
Алгоритм действий будет следующим:
Массив.Проанализируем пример кода:
МассивДляУдаления = Новый Массив;
// Шаг 1: Собираем объекты строк
Для каждого СтрокаТЗ из СписокДокументов Цикл
Если УсловиеДляУдаления(СтрокаТЗ) Тогда
МассивДляУдаления.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
// Шаг 2: Удаляем собранные строки
Для каждого СтрокаДляУдаления Из МассивДляУдаления Цикл
СписокДокументов.Удалить(СтрокаДляУдаления);
КонецЦикла;
Этот способ считается более «чистым» с точки зрения чтения кода, однако стоит помнить, что при удалении каждой строки платформа всё равно выполняет операцию сдвига данных в памяти.
Выясним причину, по которой опытные разработчики часто советуют вообще не удалять строки из текущей таблицы. Как было отмечено в дискуссии, при удалении каждой строки платформа 1С может перестраивать структуру таблицы в оперативной памяти. Если в таблице 100 000 строк и вам нужно удалить 50 000, время выполнения будет расти в геометрической прогрессии.
Вместо удаления «лишнего», мы создадим новую таблицу и перенесем туда «нужное». Рассмотрим этот процесс:
// Создаем копию структуры исходной таблицы
НоваяТаблица = СписокДокументов.СкопироватьКолонки();
Для каждого СтрокаТЗ из СписокДокументов Цикл
// Если строка НЕ подлежит удалению (т.е. она нам нужна)
Если НЕ НужноУдалитьСтроку(СтрокаТЗ) Тогда
НоваяСтрока = НоваяТаблица.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
КонецЕсли;
КонецЦикла;
// Заменяем старую таблицу новой
СписокДокументов = НоваяТаблица;
Преимущество: этот метод работает значительно быстрее на больших массивах данных, так как операция добавления строки в конец таблицы менее затратна, чем удаление из середины.
Если ваша задача — избавиться от дублей по определенному набору колонок, мы можем использовать встроенный метод Свернуть(). Он работает максимально быстро, так как реализован на уровне платформы.
Рассмотрим синтаксис: ТаблицаЗначений.Свернуть("КолонкиГруппировки", "КолонкиСуммирования"). Посмотрим на ситуацию из сообщения (0): автору нужно было удалить повторяющиеся значения в одной колонке, но при этом строки не считались одинаковыми из-за других колонок. Если мы укажем в группировке все значимые колонки, дубли исчезнут автоматически.
// Сворачиваем таблицу по всем колонкам, чтобы убрать полные дубликаты
СтрокаКолонок = "НомерДокумента, ДатаДокумента, Контрагент, Сумма";
СписокДокументов.Свернуть(СтрокаКолонок);
Для продвинутых сценариев, когда нужно удалить очень много строк, разберем еще один метод. Мы можем добавить в таблицу временную колонку ПометкаУдаления (тип Булево), проставить в ней Истина для ненужных строк, затем отсортировать таблицу так, чтобы все помеченные строки оказались в конце, и просто обрезать количество строк в таблице. Однако в 1С 8.3 чаще используют метод Скопировать() с отбором через структуру, что является наиболее производительным вариантом.
Подведем итог: выбор метода зависит от объема данных и сложности условия. Для небольших таблиц идеально подходит обратный цикл или массив строк. Для высоконагруженных участков кода — копирование в новую таблицу или использование запросов к временным таблицам.