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