Как эффективно сравнить два массива или коллекции в 1С и проставить отметки совпадения?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

В практике разработки на платформе 1С:Предприятие 8 часто возникает задача сопоставления двух списков данных. Например, у нас есть таблица с номенклатурой, и нам нужно автоматически проставить «галочки» (флаги) напротив тех позиций, которые присутствуют в другом списке (например, в регистре сведений со скидками или в массиве выбранных товаров). На первый взгляд задача кажется простой, но при больших объемах данных выбор неправильного алгоритма может привести к катастрофическому падению производительности. Если же сопоставляемые данные содержат небольшие различия в написании, решить эту задачу поможет алгоритм интеллектуального сравнения строк.

В этой статье мы подробно разберем несколько подходов к сравнению массивов и коллекций, проанализируем их эффективность и научимся избегать типичных ошибок, которые превращают быструю обработку в бесконечное ожидание.

Способ 1. Использование функций Библиотеки стандартных подсистем (БСП)

Если ваша конфигурация построена на базе БСП, прежде чем изобретать велосипед, стоит обратиться к стандартным инструментам. В модуле ОбщегоНазначенияКлиентСервер существуют готовые методы для работы с массивами. Проанализируем самые полезные из них:

  1. РазностьМассивов. Позволяет найти элементы, которые есть в первом списке, но отсутствуют во втором.
  2. ПересечениеМассивов. Возвращает массив элементов, которые присутствуют в обоих списках одновременно. Это именно то, что часто требуется для решения задачи автора: найти общие позиции.

Пример использования функции пересечения для проставления флагов в таблице:


// Предположим, МассивНоменклатуры2 содержит список позиций со скидками
// ОбъектТаблица — это табличная часть на форме
МассивСовпадений = ОбщегоНазначенияКлиентСервер.ПересечениеМассивов(МассивНоменклатуры1, МассивНоменклатуры2);

Для Каждого СтрокаТаблицы Из ОбъектТаблица Цикл
    Если МассивСовпадений.Найти(СтрокаТаблицы.Номенклатура) <> Неопределено Тогда
        СтрокаТаблицы.Выбран = Истина;
    КонецЕсли;
КонецЦикла;

Важно помнить, что метод Найти() для массива имеет линейную сложность O(n). Если оба массива содержат тысячи элементов, такой цикл будет работать медленно, так как для каждой строки таблицы система будет заново перебирать весь массив совпадений. Для быстрого поиска и изучения других готовых функций платформы полезно использовать справочник по методам БСП.

Способ 2. Сравнение через запросы (оптимальный путь)

Если данные, с которыми нужно сравнить ваш список, уже находятся в базе данных (например, в регистре сведений), самым эффективным решением будет использование запроса с ЛЕВЫМ СОЕДИНЕНИЕМ. Вместо того чтобы выгружать данные из регистра в массив, а потом сравнивать их в коде, мы поручим эту работу СУБД.

Рассмотрим алгоритм действий:

  1. Помещаем ваш исходный список (Массив 1) во временную таблицу.
  2. Выполняем левое соединение этой временной таблицы с таблицей регистра (Массив 2).
  3. Если поле из правой таблицы (регистра) не равно NULL, значит, позиция совпала.

Пример структуры такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВременнаяТаблица.Номенклатура КАК Номенклатура
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |	&МассивНоменклатуры КАК ВременнаяТаблица
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_ИсходныеДанные.Номенклатура КАК Номенклатура,
    |	ВЫБОР
    |		КОГДА СкидкиСрезПоследних.Номенклатура ЕСТЬ НЕ NULL
    |			ТОГДА ИСТИНА
    |		ИНАЧЕ ЛОЖЬ
    |	КОНЕЦ КАК ЕстьСкидка
    |ИЗ
    |	ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
    |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Скидки.СрезПоследних(&Период, ) КАК СкидкиСрезПоследних
    |		ПО ВТ_ИсходныеДанные.Номенклатура = СкидкиСрезПоследних.Номенклатура";

Запрос.УстановитьПараметр("МассивНоменклатуры", Массив1);
Запрос.УстановитьПараметр("Период", ТекущаяДата());

Результат = Запрос.Выполнить().Выгрузить();
// Теперь результат можно загрузить в таблицу или использовать для обновления флагов

Преимущество: Этот метод работает максимально быстро, так как СУБД использует индексы для соединения таблиц. Мы избегаем лишних циклов в коде 1С.

Способ 3. Оптимизация через объект «Соответствие» (для больших массивов в памяти)

Если оба массива уже находятся в памяти и вы не хотите использовать запрос, разберем способ с применением объекта Соответствие. Это «секретное оружие» для задач поиска, так как поиск по ключу в соответствии происходит практически мгновенно (сложность O(1)), в отличие от Массив.Найти().

Выясним причину ускорения: Соответствие внутри использует хеш-таблицу. Когда мы запрашиваем значение по ключу, система не перебирает все элементы, а сразу обращается к нужному адресу в памяти.


// 1. Заполняем соответствие элементами второго массива
СловарьСовпадений = Новый Соответствие;
Для Каждого Элемент Из Массив2 Цикл
    СловарьСовпадений.Вставить(Элемент, Истина);
КонецЦикла;

// 2. Быстро проверяем наличие элементов первого массива в словаре
Для Каждого СтрокаТаблицы Из ТаблицаНоменклатуры Цикл
    Если СловарьСовпадений.Получить(СтрокаТаблицы.Номенклатура) <> Неопределено Тогда
        СтрокаТаблицы.Выбран = Истина;
    КонецЕсли;
КонецЦикла;

Этот метод незаменим, когда вам нужно сравнить коллекции на клиенте или в случаях, когда использование временных таблиц в запросе нецелесообразно по архитектурным причинам.

Способ 4. Алгоритм «Свертка ТЗ»

Интересный способ предложили участники форума для тех случаев, когда нужно не просто найти совпадения, а проанализировать различия в составах. Рассмотрим его:

  1. Создаем таблицу значений с колонками Объект и Счетчик.
  2. Добавляем элементы первого массива со значением Счетчик = 1.
  3. Добавляем элементы второго массива со значением Счетчик = -1 (или другим маркером).
  4. Выполняем метод Свернуть("Объект", "Счетчик").

Проанализируем результат после свертки:

Критическая ошибка: Запрос в цикле

Разберем ситуацию из сообщения №13 на форуме. Автор привел код, где внутри цикла обхода номенклатуры вызывается функция ЗапросСкидок(). Это классический пример антипаттерна, который убивает производительность системы.

Почему это плохо? Каждый вызов функции — это отдельное обращение к серверу базы данных. Если в вашей таблице 1000 строк, система выполнит 1000 сетевых запросов. Время ожидания ответа от СУБД в сумме может занять минуты.

Как исправить? Выполните один общий запрос до начала цикла. Получите все необходимые данные (цены, скидки) по списку номенклатуры сразу, выгрузите их в Соответствие или ТаблицуЗначений с индексом, а затем внутри цикла просто берите готовые данные из памяти.

Резюме

Для эффективного сравнения двух массивов в 1С придерживайтесь следующих правил:

  1. Если данные в базе — используйте Запрос и Соединение.
  2. Если данные в памяти и их много — используйте Соответствие для быстрого поиска.
  3. Если нужно найти разницу или пересечение небольших массивов — используйте функции БСП.
  4. Никогда не выполняйте запросы к базе данных внутри цикла перебора массива.

Выбор правильного метода зависит от контекста задачи, но понимание того, как работают коллекции «под капотом», позволит вам писать код, который работает стабильно при любых нагрузках.

← На главную