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