В процессе разработки на платформе 1С:Предприятие 8 часто возникает задача обработки коллекций данных, полученных из различных источников: внешних API, файлов или сложных запросов. Одной из типичных проблем является необходимость отфильтровать дублирующиеся записи в массиве структур, например, когда из полного списка товаров нужно выделить только уникальные родительские категории с их кодами.
В этой статье мы подробно разберем несколько эффективных способов решения этой задачи, проанализируем их производительность и выясним, какой метод лучше использовать в зависимости от объема данных.
Это самый популярный и интуитивно понятный способ для разработчиков 1С. Если у вас уже есть массив структур, мы можем быстро преобразовать его в ТаблицаЗначений, которая обладает мощным встроенным методом Свернуть().
Рассмотрим алгоритм реализации этого метода:
Свернуть(), указывая колонки группировки.Проанализируем пример кода:
// Предположим, МассивДанных уже заполнен структурами
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("РодительскаяКатегорияКод");
ТЗ.Колонки.Добавить("РодительскаяКатегория");
// Переносим данные из массива в ТЗ
Для Каждого Элемент Из МассивДанных Цикл
НоваяСтрока = ТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Элемент);
КонецЦикла;
// Сворачиваем таблицу по двум колонкам
// После выполнения в ТЗ останутся только уникальные пары Код + Наименование
ТЗ.Свернуть("РодительскаяКатегорияКод, РодительскаяКатегория");
Важный нюанс: метод Свернуть() удаляет все колонки, которые не указаны в параметрах метода (если не указаны колонки для суммирования). Поэтому в параметрах группировки нужно перечислять все поля, которые вы хотите сохранить для дальнейшей работы.
Если данные изначально выбираются из базы данных, как в примере из сообщения 4, то эффективнее всего отсечь дубли еще на этапе выполнения запроса. Для этого используется ключевое слово РАЗЛИЧНЫЕ.
Однако, если данные уже находятся в оперативной памяти (в массиве), мы все равно можем использовать мощь механизма запросов. Для этого массив (или таблицу значений) можно передать в запрос как параметр и поместить во временную таблицу.
Посмотрим, как это реализовать:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВТ_Данные.РодительскаяКатегорияКод КАК Код,
| ВТ_Данные.РодительскаяКатегория КАК Наименование
|ИЗ
| &ТаблицаДанных КАК ВТ_Данные";
// В качестве параметра передаем ТЗ, сформированную из массива
Запрос.УстановитьПараметр("ТаблицаДанных", ТЗ);
Результат = Запрос.Выполнить().Выгрузить();
Этот подход особенно удобен, если помимо уникальности вам нужно выполнить дополнительную сортировку или фильтрацию данных средствами SQL.
При работе с очень большими массивами (десятки и сотни тысяч записей) методы с ТаблицаЗначений могут работать медленнее из-за накладных расходов на создание объекта. В таких случаях программисты используют объект Соответствие.
Основное преимущество Соответствие заключается в том, что поиск по ключу в нем реализован через хеш-таблицы, что обеспечивает практически мгновенный доступ (сложность O(1)). Мы можем использовать составной ключ для обеспечения уникальности по нескольким полям.
Разберем пример реализации:
УникальныеЗаписи = Новый Соответствие;
Для Каждого Запись Из МассивДанных Цикл
// Формируем уникальный ключ из кода и наименования
Ключ = Строка(Запись.РодительскаяКатегорияКод) + "___" + Запись.РодительскаяКатегория;
// Если ключ уже есть, значение просто перезапишется, дубли не возникнут
УникальныеЗаписи.Вставить(Ключ, Запись);
КонецЦикла;
// Теперь из соответствия можно получить чистый массив уникальных структур
ИтоговыйМассив = Новый Массив;
Для Каждого КлючИЗначение Из УникальныеЗаписи Цикл
ИтоговыйМассив.Добавить(КлючИЗначение.Значение);
КонецЦикла;
Этот метод считается наиболее "профессиональным" с точки зрения алгоритмической сложности. Он работает за линейное время O(n), в отличие от вложенных циклов перебора массива, которые имеют квадратичную сложность O(n²).
Проанализируем типичные ошибки, которые допускают разработчики при решении данной задачи:
ВернутьРодителя(Товар) внутри цикла. Это крайне неэффективно, так как при наличии 1000 товаров будет выполнено 1000 дополнительных запросов к базе. Правильное решение: выгружать родителя и его код сразу в основном запросе через точку (например, Товар.Родитель.Наименование).Число, а в другом — Строка, система посчитает их разными. Рекомендуется принудительно приводить ключевые поля к строке с помощью функции Строка() или СокращенноЛП().Соответствие чувствителен к регистру. Если названия категорий могут отличаться регистром (например, "Обувь" и "обувь"), перед вставкой в ключ используйте функцию ВРег().Если ваша конфигурация построена на базе современных типовых решений (УТ 11, ERP, БП 3.0), в ней наверняка присутствует БСП. Перед тем как писать свой велосипед, проверьте наличие готовых инструментов в общем модуле ОбщегоНазначения.
Функция ОбщегоНазначения.УдалитьПовторяющиесяЭлементыМассива(Массив) позволяет быстро очистить массив от дублей, если элементами являются простые типы или ссылки. Однако для массива структур она не подойдет напрямую, так как структуры — это ссылочные объекты, и две разные структуры с одинаковыми полями не считаются идентичными для этой функции.
Для работы со структурами в БСП лучше использовать комбинацию функций конвертации ТаблицаЗначенийВМассив и МассивВТаблицуЗначений, чтобы применить метод Свернуть, не занимаясь ручным перебором строк.
Выбор конкретного метода зависит от ваших условий. Для небольших коллекций и удобства поддержки кода идеально подходит Таблица Значений. Для высоконагруженных систем и огромных массивов выбирайте Соответствие. А если данные все равно нужно получать из БД — используйте Запрос с ключевым словом РАЗЛИЧНЫЕ.