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