При разработке алгоритмов обработки табличных данных в 1С разработчики нередко сталкиваются с ситуацией, когда при обходе строк табличной части или таблицы значений часть неподходящих записей удаляется, а некоторые строки ошибочно пропускаются и остаются в таблице. Проанализируем, почему возникает такое поведение, разберем типичные ошибки и рассмотрим надежные способы корректного удаления строк.
Посмотрим на типичный фрагмент кода, приводящий к ошибке:
Для Каждого Стр Из Коллекция Цикл
Если Стр.Условие Тогда
Коллекция.Удалить(Стр.НомерСтроки);
КонецЕсли;
КонецЦикла;
В этом примере кроются сразу две критические проблемы:
Для Каждого последовательно перемещает внутренний указатель с позиции 0 на позицию 1, затем 2 и так далее. Когда вызывается метод Удалить(), все последующие строки сдвигаются на одну позицию к началу коллекции (вверх). В результате строка, следовавшая непосредственно за удаленной, занимает ее индекс. Цикл же переходит к следующему шагу и тем самым перескакивает через эту сместившуюся строку. Если несколько строк подряд подлежат удалению, ровно половина из них останется в таблице.НомерСтроки вместо индекса. Свойство НомерСтроки табличной части нумеруется с 1, тогда как программный индекс строк в коллекциях 1С начинается с 0. Метод Удалить() может принимать либо объект самой строки, либо целочисленный индекс (от 0 до Количество - 1). Передача НомерСтроки приводит к удалению строки со сдвигом на +1, а на последних строках вызывает исключение выхода индекса за границы массива.Разберем по шагам наиболее универсальный и безопасный подход. Метод Удалить() платформы 1С умеет принимать саму ссылку на объект строки (СтрокаТабличнойЧасти или СтрокаТаблицыЗначений). Поэтому мы можем в первом проходе собрать все строки, подлежащие удалению, в обычный массив, а затем во втором цикле удалить их из исходной коллекции.
Посмотрим на пример реализации:
МассивУдаляемых = Новый Массив;
// Первый проход: собираем ссылки на строки, удовлетворяющие условию
Для Каждого Стр Из ОбработкаОбъектЗначенияСвойств.СвойстваИЗначения Цикл
Если Стр.Свойство.ДляНоменклатурыТипХарактеристики <> Ссылка.ТипХарактеристики Тогда
МассивУдаляемых.Добавить(Стр);
КонецЕсли;
КонецЦикла;
// Второй проход: удаляем собранные строки
Для Каждого Стр Из МассивУдаляемых Цикл
ОбработкаОбъектЗначенияСвойств.СвойстваИЗначения.Удалить(Стр);
КонецЦикла;
Данный подход нагляден, прост в отладке и полностью исключает проблемы со смещением индексов при обходе.
Рассмотрим подробнее альтернативное решение — обход коллекции с конца к началу. Когда мы удаляем элементы с конца таблицы, сдвиг нижележащих строк уже никак не влияет на индексы тех элементов, которые расположены ближе к началу и еще не были проверены.
Поскольку в синтаксисе языка 1С цикл Для ... По работает строго с шагом +1 и не поддерживает отрицательный шаг, обратный обход реализуется с помощью цикла Пока:
Таблица = ОбработкаОбъектЗначенияСвойств.СвойстваИЗначения;
Индекс = Таблица.Количество() - 1;
Пока Индекс >= 0 Цикл
Стр = Таблица[Индекс];
Если Стр.Свойство.ДляНоменклатурыТипХарактеристики <> Ссылка.ТипХарактеристики Тогда
Таблица.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Обратите внимание: доступ к строке осуществляется через квадратные скобки по индексу Таблица[Индекс], а в метод Удалить() передается текущее значение переменной Индекс.
Еще один рабочий способ удаления — обход от начала к концу с ручным контролем индекса. Индекс увеличивается только в том случае, если строка не была удалена. Если же строка удалена, следующая строка автоматически встает на текущий индекс, и смещать его не нужно.
Посмотрим на пример кода:
Таблица = ОбработкаОбъектЗначенияСвойств.СвойстваИЗначения;
Индекс = 0;
Пока Индекс < Таблица.Количество() Цикл
Стр = Таблица[Индекс];
Если Стр.Свойство.ДляНоменклатурыТипХарактеристики <> Ссылка.ТипХарактеристики Тогда
Таблица.Удалить(Индекс);
Иначе
Индекс = Индекс + 1;
КонецЕсли;
КонецЦикла;
Проанализируем производительность при работе с большими объемами данных. Метод Удалить() при каждом вызове физически сдвигает массив строк в памяти. Если таблица содержит тысячи строк и удаляется значительная их часть, постоянные вызовы Удалить() могут существенно замедлить выполнение операции.
Для ускорения работы применимы следующие практики:
ТипХарактеристики правильнее наложить непосредственно в секции ГДЕ текста запроса. В таком случае ненужные данные вовсе не будут считываться в память и отпадет необходимость их построчного удаления.НайтиСтроки(). Если условие удаления представляет собой точное равенство конкретному значению колонки, эффективнее найти строки методом НайтиСтроки(), передав структуру отбора, и затем в цикле удалить только найденные позиции.Скопировать(), очистить исходную коллекцию методом Очистить() и загрузить отфильтрованные данные обратно.