В процессе разработки алгоритмов часто возникает задача: необходимо накапливать множество пар элементов (например, связок «Номенклатура — Характеристика», «Узел А — Узел Б», связей в графах или соответствий объектов) и периодически проверять, существует ли конкретная пара в коллекции. Обычные массивы и списки значений для этой задачи подходят плохо, так как линейный перебор O(N) на больших объемах данных приводит к существенным просадкам производительности.
Разберем основные и наиболее производительные способы решения этой задачи в платформе 1С:Предприятие, их особенности, ограничения и примеры реализации.
Индексированная таблица значений — классический и наиболее наглядный вариант, когда требуется поиск по двум колонкам. Добавление составного индекса переводит поиск через метод НайтиСтроки() в режим быстрого поиска по индексу.
Рассмотрим пример создания и поиска по составному индексу:
// Создаем таблицу значений и описываем колонки
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("ЭлементА");
ТЗ.Колонки.Добавить("ЭлементБ");
// Добавляем составной индекс по двум колонкам
ТЗ.Колонки.Индексы.Добавить("ЭлементА, ЭлементБ");
// Заполняем данными
СтрокаТЗ = ТЗ.Добавить();
СтрокаТЗ.ЭлементА = Ссылка1;
СтрокаТЗ.ЭлементБ = "Параметр1";
// Проверка наличия пары
Отбор = Новый Структура;
Отбор.Вставить("ЭлементА", ИскомыйЭлементА);
Отбор.Вставить("ЭлементБ", ИскомыйЭлементБ);
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
Если НайденныеСтроки.Количество() > 0 Тогда
// Пара существует
КонецЕсли;
Важные нюансы при работе с индексами таблицы значений:
НайтиСтроки() задействует составной индекс только в том случае, если в структуре отбора переданы все поля индекса. Если передать только одно поле, платформа выполнит полный перебор строк.ТаблицаЗначений доступен только на стороне сервера или в толстом клиенте. В тонком клиенте использовать его нельзя.< или > напрямую нельзя. Для ссылок сравнение выполняют по строковому представлению идентификатора через XMLСтрока(Ссылка).Коллекция Соответствие работает на основе хэш-таблицы в оперативной памяти, что обеспечивает поиск со скоростью O(1). Для хранения пар можно использовать двухуровневую структуру: внешнее соответствие хранит первый элемент пары в качестве ключа, а в качестве значения — вложенное соответствие со вторыми элементами.
Посмотрим на пример инициализации, добавления и быстрой проверки пары:
// Инициализация структуры
Пары = Новый Соответствие;
// Добавление новой пары
Вложенное = Пары[ЭлементА];
Если Вложенное = Неопределено Тогда
Вложенное = Новый Соответствие;
Пары[ЭлементА] = Вложенное;
КонецЕсли;
Вложенное[ЭлементБ] = Истина; // Или вес/дополнительное значение связи
// Проверка наличия пары
Если Пары[ИскомыйА] <> Неопределено И Пары[ИскомыйА][ИскомыйБ] <> Неопределено Тогда
// Пара существует в коллекции
КонецЕсли;
Проанализируем преимущества и недостатки данного подхода:
Удалить() без необходимости сдвига массива строк, как в таблицах.Соответствие может создавать ощутимые накладные расходы по памяти по сравнению с плоскими структурами.Если для алгоритма важна только проверка факта существования пары «А–Б» и не требуется получать список всех «Б» для конкретного «А», оптимально использовать одно плоское Соответствие, сформировав уникальный строковый ключ.
Если среди значений присутствуют ссылочные типы, нельзя использовать простое приведение Строка(Ссылка), так как наименования могут совпадать или изменяться. Надежным идентификатором ссылки является вызов XMLСтрока(Ссылка).
Разберем пример реализации:
// Функция формирования уникального ключа пары
Функция ПолучитьКлючПары(ЗначениеА, ЗначениеБ)
СтрА = ?(ЗначениеЗаполнено(ЗначениеА) И ТипЗнч(ЗначениеА) = Тип("СправочникСсылка.Номенклатура"),
XMLСтрока(ЗначениеА), Строка(ЗначениеА));
СтрБ = ?(ЗначениеЗаполнено(ЗначениеБ) И ТипЗнч(ЗначениеБ) = Тип("СправочникСсылка.ХарактеристикиНоменклатуры"),
XMLСтрока(ЗначениеБ), Строка(ЗначениеБ));
Возврат СтрА + "#" + СтрБ;
КонецФункции
// Использование
МножествоПар = Новый Соответствие;
// Добавление
Ключ = ПолучитьКлючПары(ТоварСсылка, ХарактеристикаСсылка);
МножествоПар.Вставить(Ключ, Истина);
// Проверка
Если МножествоПар[ПолучитьКлючПары(ПроверяемыйТовар, ПроверяемаяХарактеристика)] <> Неопределено Тогда
// Пара найдена
КонецЕсли;
Если строки получаются слишком длинными и требуется оптимизировать расход памяти, для получения ключа можно использовать встроенный объект ХешированиеДанных (например, с алгоритмом ХешФункция.MD5 или ХешФункция.CRC32).
Если в процессе работы алгоритма требуется проверить наличие не одной пары, а сопоставить целый массив входящих пар с существующей выборкой, циклическая проверка в коде 1С уступает запросному механизму.
Выясним, как организовать пакетную проверку:
ТаблицаЗначений.Запрос как параметр.ИНДЕКСИРОВАТЬ ПО для полей связки.ВНУТРЕННЕЕ СОЕДИНЕНИЕ или условие ГДЕ (Поле1, Поле2) В (ВЫБРАТЬ ...).Такой подход переносит нагрузку по сравнению сложных ключей на движок СУБД, что дает максимальную производительность при пакетной обработке десятков и сотен тысяч записей.