В практике разработки на платформе 1С:Предприятие 8 часто возникает задача сопоставления двух списков данных. Например, у нас есть таблица с номенклатурой, и нам нужно автоматически проставить «галочки» (флаги) напротив тех позиций, которые присутствуют в другом списке (например, в регистре сведений со скидками или в массиве выбранных товаров). На первый взгляд задача кажется простой, но при больших объемах данных выбор неправильного алгоритма может привести к катастрофическому падению производительности. Если же сопоставляемые данные содержат небольшие различия в написании, решить эту задачу поможет алгоритм интеллектуального сравнения строк.
В этой статье мы подробно разберем несколько подходов к сравнению массивов и коллекций, проанализируем их эффективность и научимся избегать типичных ошибок, которые превращают быструю обработку в бесконечное ожидание.
Если ваша конфигурация построена на базе БСП, прежде чем изобретать велосипед, стоит обратиться к стандартным инструментам. В модуле ОбщегоНазначенияКлиентСервер существуют готовые методы для работы с массивами. Проанализируем самые полезные из них:
Пример использования функции пересечения для проставления флагов в таблице:
// Предположим, МассивНоменклатуры2 содержит список позиций со скидками
// ОбъектТаблица — это табличная часть на форме
МассивСовпадений = ОбщегоНазначенияКлиентСервер.ПересечениеМассивов(МассивНоменклатуры1, МассивНоменклатуры2);
Для Каждого СтрокаТаблицы Из ОбъектТаблица Цикл
Если МассивСовпадений.Найти(СтрокаТаблицы.Номенклатура) <> Неопределено Тогда
СтрокаТаблицы.Выбран = Истина;
КонецЕсли;
КонецЦикла;
Важно помнить, что метод Найти() для массива имеет линейную сложность O(n). Если оба массива содержат тысячи элементов, такой цикл будет работать медленно, так как для каждой строки таблицы система будет заново перебирать весь массив совпадений. Для быстрого поиска и изучения других готовых функций платформы полезно использовать справочник по методам БСП.
Если данные, с которыми нужно сравнить ваш список, уже находятся в базе данных (например, в регистре сведений), самым эффективным решением будет использование запроса с ЛЕВЫМ СОЕДИНЕНИЕМ. Вместо того чтобы выгружать данные из регистра в массив, а потом сравнивать их в коде, мы поручим эту работу СУБД.
Рассмотрим алгоритм действий:
NULL, значит, позиция совпала.Пример структуры такого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВременнаяТаблица.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &МассивНоменклатуры КАК ВременнаяТаблица
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ИсходныеДанные.Номенклатура КАК Номенклатура,
| ВЫБОР
| КОГДА СкидкиСрезПоследних.Номенклатура ЕСТЬ НЕ NULL
| ТОГДА ИСТИНА
| ИНАЧЕ ЛОЖЬ
| КОНЕЦ КАК ЕстьСкидка
|ИЗ
| ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Скидки.СрезПоследних(&Период, ) КАК СкидкиСрезПоследних
| ПО ВТ_ИсходныеДанные.Номенклатура = СкидкиСрезПоследних.Номенклатура";
Запрос.УстановитьПараметр("МассивНоменклатуры", Массив1);
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Результат = Запрос.Выполнить().Выгрузить();
// Теперь результат можно загрузить в таблицу или использовать для обновления флагов
Преимущество: Этот метод работает максимально быстро, так как СУБД использует индексы для соединения таблиц. Мы избегаем лишних циклов в коде 1С.
Если оба массива уже находятся в памяти и вы не хотите использовать запрос, разберем способ с применением объекта Соответствие. Это «секретное оружие» для задач поиска, так как поиск по ключу в соответствии происходит практически мгновенно (сложность O(1)), в отличие от Массив.Найти().
Выясним причину ускорения: Соответствие внутри использует хеш-таблицу. Когда мы запрашиваем значение по ключу, система не перебирает все элементы, а сразу обращается к нужному адресу в памяти.
// 1. Заполняем соответствие элементами второго массива
СловарьСовпадений = Новый Соответствие;
Для Каждого Элемент Из Массив2 Цикл
СловарьСовпадений.Вставить(Элемент, Истина);
КонецЦикла;
// 2. Быстро проверяем наличие элементов первого массива в словаре
Для Каждого СтрокаТаблицы Из ТаблицаНоменклатуры Цикл
Если СловарьСовпадений.Получить(СтрокаТаблицы.Номенклатура) <> Неопределено Тогда
СтрокаТаблицы.Выбран = Истина;
КонецЕсли;
КонецЦикла;
Этот метод незаменим, когда вам нужно сравнить коллекции на клиенте или в случаях, когда использование временных таблиц в запросе нецелесообразно по архитектурным причинам.
Интересный способ предложили участники форума для тех случаев, когда нужно не просто найти совпадения, а проанализировать различия в составах. Рассмотрим его:
Объект и Счетчик.Счетчик = 1.Счетчик = -1 (или другим маркером).Свернуть("Объект", "Счетчик").Проанализируем результат после свертки:
Счетчик = 0 — элемент был в обоих массивах (совпадение).Счетчик = 1 — элемент был только в первом массиве.Счетчик = -1 — элемент был только во втором массиве.Разберем ситуацию из сообщения №13 на форуме. Автор привел код, где внутри цикла обхода номенклатуры вызывается функция ЗапросСкидок(). Это классический пример антипаттерна, который убивает производительность системы.
Почему это плохо? Каждый вызов функции — это отдельное обращение к серверу базы данных. Если в вашей таблице 1000 строк, система выполнит 1000 сетевых запросов. Время ожидания ответа от СУБД в сумме может занять минуты.
Как исправить? Выполните один общий запрос до начала цикла. Получите все необходимые данные (цены, скидки) по списку номенклатуры сразу, выгрузите их в Соответствие или ТаблицуЗначений с индексом, а затем внутри цикла просто берите готовые данные из памяти.
Для эффективного сравнения двух массивов в 1С придерживайтесь следующих правил:
Выбор правильного метода зависит от контекста задачи, но понимание того, как работают коллекции «под капотом», позволит вам писать код, который работает стабильно при любых нагрузках.