Как найти элемент в СписокЗначений: НайтиПоЗначению, цикл и Соответствие

· 9 мин

Содержание

Чтобы найти значение в списке значений 1С, вызовите Список.НайтиПоЗначению(Значение). Метод вернёт первый подходящий ЭлементСпискаЗначений или Неопределено, если такого значения нет. Само значение потом берут из свойства .Значение, номер позиции — через Список.Индекс(Элемент).

Это работает для одного-двух поисков. Если внутри цикла вы ищете сотни раз по большому списку, линейный поиск съедает время. Тогда список один раз перекладывают в Соответствие и ищут по ключу. Всё ниже относится к платформе 8.3 целиком и не зависит от конфигурации: БП, ЗУП, УТ или своя разработка.

Методы поиска в объекте СписокЗначений

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

МетодЧто ищетЧто возвращает
НайтиПоЗначению(Значение)Элемент с указанным значениемЭлементСпискаЗначений или Неопределено
НайтиПоИдентификатору(Идентификатор)Элемент по его идентификаторуЭлементСпискаЗначений или Неопределено
Индекс(Элемент)Позицию уже найденного элементаЧисло, нумерация с 0
ВыгрузитьЗначения() + Массив.Найти()Значение в массивеИндекс или Неопределено

НайтиПоЗначению — основной способ

Список = Новый СписокЗначений;
Список.Добавить("Москва", "Склад Москва");
Список.Добавить("Казань", "Склад Казань");
Список.Добавить("Самара", "Склад Самара");

Элемент = Список.НайтиПоЗначению("Казань");
Если Элемент = Неопределено Тогда
    Сообщить("Значение не найдено");
Иначе
    Сообщить("Найдено: " + Элемент.Представление
        + ", индекс " + Формат(Список.Индекс(Элемент), "ЧН=0; ЧГ=0"));
КонецЕсли;

Код выведет Найдено: Склад Казань, индекс 1. Индекс равен 1, потому что нумерация начинается с нуля.

💡 Если в списке есть дубли, метод вернёт первый по порядку. Остальные он не покажет — для них нужен цикл.

НайтиПоИдентификатору — для элементов формы

Этот метод нужен, когда список значений выведен на форму. Свойство ТекущаяСтрока у элемента формы хранит не сам элемент, а его идентификатор. По нему и ищут:

&НаКлиенте
Процедура ПоказатьТекущий(Команда)
    Идентификатор = Элементы.Склады.ТекущаяСтрока;
    Если Идентификатор = Неопределено Тогда
        Возврат;
    КонецЕсли;
    Элемент = Склады.НайтиПоИдентификатору(Идентификатор);
    Сообщить(Элемент.Представление);
КонецПроцедуры

Здесь Склады — реквизит формы типа СписокЗначений, а Элементы.Склады — таблица на форме. Обратная операция — Элемент.ПолучитьИдентификатор(): так программно переводят курсор на нужную строку.

Поиск через массив

ВыгрузитьЗначения() возвращает массив значений в том же порядке. У массива есть метод Найти, который возвращает индекс:

Значения = Список.ВыгрузитьЗначения();
Индекс = Значения.Найти("Самара"); // 2 или Неопределено

Индексы массива и списка совпадают, поэтому по найденному индексу можно взять элемент: Список[Индекс]. Отдельного выигрыша по скорости этот путь не даёт: поиск всё так же идёт подряд. Он удобен, когда массив всё равно нужен дальше, например, для параметра запроса.

Сложность и производительность поиска по списку значений

НайтиПоЗначению проверяет элементы подряд, от первого до совпадения. Значит, время растёт линейно с длиной списка: O(n). Для 50 элементов это незаметно. Для 50 000 элементов и поиска внутри цикла — уже проблема.

Посчитаем на примере. Есть список из 10 000 контрагентов и 10 000 строк документа. Для каждой строки нужно проверить, есть ли контрагент в списке.

  • 🐢 НайтиПоЗначению в цикле: в худшем случае 10 000 × 10 000 = 100 000 000 сравнений.
  • ⚡ Соответствие: 10 000 вставок на подготовку и 10 000 обращений по ключу. Каждое обращение по ключу идёт по хешу и почти не зависит от размера коллекции.

Разница — на порядки, и она растёт с объёмом данных.

Цикл на встроенном языке медленнее методов платформы

Ручной обход Для Каждого Элемент Из Список Цикл делает то же самое, что НайтиПоЗначению, но каждое сравнение выполняет интерпретатор встроенного языка. Метод платформы работает в её собственном коде и обычно заметно быстрее. Поэтому цикл пишут только там, где метода нет: поиск по представлению, по пометке, по условию.

Как замерить у себя

Реальные цифры зависят от сервера, типа значений и длины списка. Проверяйте замером, а не на глаз:

Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();

Для Сч = 1 По 1000 Цикл
    Элемент = Список.НайтиПоЗначению(ИскомоеЗначение);
КонецЦикла;

Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("1000 поисков: " + Формат(Длительность, "ЧН=0; ЧГ=0") + " мс");

Запустите тот же замер для Соответствие.Получить() и сравните. Для точной картины по всей процедуре подойдёт замер производительности в отладчике конфигуратора или EDT.

Сравнение методов поиска значения в списке

СпособСложность одного поискаПодготовкаКогда выбиратьОграничения
НайтиПоЗначениюO(n)Не нужнаРазовый поиск, короткий списокТолько по значению, только первый элемент
НайтиПоИдентификатору—Не нужнаРабота с текущей строкой на формеНужен идентификатор элемента
Цикл Для КаждогоO(n)Не нужнаПоиск по представлению, пометке, условиюСамый медленный
ВыгрузитьЗначения + НайтиO(n)Выгрузка массива, O(n)Массив нужен дальшеВыигрыша по скорости нет
СоответствиеПочти O(1)Заполнение, O(n)Много поисков по одному спискуКлюч должен быть уникальным
Сортировка + двоичный поискO(log n)СортировкаЧисла и даты, очень длинные спискиНе работает со ссылками, код пишете сами

Правило простое. Один-два поиска — НайтиПоЗначению. Поиск в цикле по одному и тому же списку — Соответствие. Поиск не по значению — цикл или соответствие с другим ключом.

Как часто вы ищете в этом списке?

Поиск по представлению, пометке и условию

НайтиПоЗначению смотрит только на свойство Значение. Для всего остального пишут цикл.

Поиск по представлению

Функция НайтиПоПредставлению(Список, Представление)
    Для Каждого Элемент Из Список Цикл
        Если Элемент.Представление = Представление Тогда
            Возврат Элемент;
        КонецЕсли;
    КонецЦикла;
    Возврат Неопределено;
КонецФункции

Сравнение строк через = учитывает регистр: «Казань» и «казань» — разные строки. Для поиска без учёта регистра сравнивайте ВРег(Элемент.Представление) = ВРег(Представление). Для поиска по части текста — СтрНайти(ВРег(Элемент.Представление), ВРег(Фрагмент)) > 0.

Все отмеченные элементы

Пометку ставит пользователь галочкой, поэтому её чаще всего и ищут:

Отмеченные = Новый Массив;
Для Каждого Элемент Из Список Цикл
    Если Элемент.Пометка Тогда
        Отмеченные.Добавить(Элемент.Значение);
    КонецЕсли;
КонецЦикла;

Готовый массив можно сразу передать в запрос: условие ГДЕ Контрагенты.Ссылка В (&Отмеченные) принимает и массив, и сам список значений.

Все элементы с одинаковым значением

Если в списке возможны дубли, собирайте все совпадения циклом. НайтиПоЗначению вернёт только первое:

Совпадения = Новый Массив;
Для Каждого Элемент Из Список Цикл
    Если Элемент.Значение = ИскомоеЗначение Тогда
        Совпадения.Добавить(Список.Индекс(Элемент));
    КонецЕсли;
КонецЦикла;

Поиск по реквизиту ссылки

Частый случай: в списке лежат ссылки на номенклатуру, а найти надо позицию с определённым артикулом. Обращение Элемент.Значение.Артикул в цикле читает объект из базы на каждой итерации. На сервере и для длинного списка лучше один запрос:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Номенклатура.Ссылка КАК Ссылка
|ИЗ
|    Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|    Номенклатура.Ссылка В (&Список)
|    И Номенклатура.Артикул = &Артикул";
Запрос.УстановитьПараметр("Список", Список);
Запрос.УстановитьПараметр("Артикул", "А-1024");
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    Элемент = Список.НайтиПоЗначению(Выборка.Ссылка);
КонецЕсли;

Ускорение поиска через Соответствие

Соответствие хранит пары «ключ — значение» и ищет по ключу через хеш. Подготовка — один проход по списку. Дальше каждый поиск почти мгновенный.

Быстрая проверка «есть ли значение в списке»

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

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

Получить() возвращает Неопределено, если ключа нет. Поэтому в качестве значения пары не кладите Неопределено — иначе «нет ключа» и «ключ есть, значение пустое» станут неотличимы. Удобный приём — класть сам элемент списка или Истина.

Ключ по другому полю

Тот же приём работает для представления. Ключом делаете ВРег(Элемент.Представление) — и получаете быстрый поиск без учёта регистра.

ПоПредставлению = Новый Соответствие;
Для Каждого Элемент Из Список Цикл
    ПоПредставлению.Вставить(ВРег(СокрЛП(Элемент.Представление)), Элемент);
КонецЦикла;
Элемент = ПоПредставлению.Получить(ВРег("склад казань"));

Что учесть при переходе на Соответствие

  • 🔑 Ключ уникален. При повторной вставке Вставить() перезапишет старое значение. Хотите первое вхождение, как у НайтиПоЗначению, — проверяйте Получить() перед вставкой.
  • 🔢 Тип ключа важен: число 1 и строка «1» — разные ключи.
  • 📋 Порядок обхода соответствия не совпадает с порядком в списке. Если порядок нужен, держите сам список и используйте соответствие только как индекс.
  • 🖥 Реквизит формы типа Соответствие создать нельзя. Строить соответствие удобно на сервере или в локальной переменной процедуры.
  • ♻️ После изменения списка соответствие устаревает. Пересоздавайте его или обновляйте вместе со списком.

А если данные в таблице значений

Когда у вас несколько колонок, список значений — не лучший выбор. У ТаблицаЗначений есть Найти() и НайтиСтроки(), а после Таблица.Индексы.Добавить("Номенклатура") поиск по проиндексированной колонке идёт без полного перебора. Это удобная альтернатива соответствию, когда ключ составной.

Ошибки при поиске значения в СписокЗначений

Метод почти всегда работает правильно. Ошибаются в том, что ищут и как проверяют результат.

Диагностика: выберите симптом

Удаление найденных элементов без пропусков

Прямой цикл по индексу сбивается: после удаления элементы сдвигаются, и следующий проскакивает. Обходите список с конца, а количество считайте один раз до цикла:

Всего = Список.Количество();
Для Сч = 1 По Всего Цикл
    Элемент = Список[Всего - Сч];
    Если Элемент.Значение = ИскомоеЗначение Тогда
        Список.Удалить(Элемент);
    КонецЕсли;
КонецЦикла;

Для списка из 5 элементов индексы пойдут 4, 3, 2, 1, 0. Удаление с конца не меняет индексы ещё не проверенных элементов.

Поиск внутри вложенного цикла

Самая дорогая ошибка не выдаёт сообщений. Код работает, но процедура заполнения документа на тысячу строк идёт минутами. Если видите НайтиПоЗначению внутри Для Каждого по строкам документа или выборке запроса — это первый кандидат на замену соответствием.

Ссылки на разные справочники

Ссылки равны, только если указывают на один и тот же объект одного типа. Ссылка на элемент справочника Контрагенты никогда не совпадёт со ссылкой на Организации, даже с одинаковым наименованием. Если список собирали из разных источников, проверьте тип через ТипЗнч().

Частые вопросы

Есть ли у списка значений метод для поиска по представлению?
Нет. НайтиПоЗначению проверяет только значение. По представлению ищут циклом или через соответствие, где ключ — представление.
Как проверить, есть ли значение в списке, одной строкой?
Список.НайтиПоЗначению(Значение) <> Неопределено. Выражение вернёт Истина, если значение есть.
Учитывает ли НайтиПоЗначению регистр строк?
Да. Строки сравниваются с учётом регистра. Для поиска без учёта регистра приводите строки к ВРег() и используйте цикл или соответствие.
Можно ли передать список значений в запрос вместо массива?
Да. В условии В (&Параметр) платформа принимает и массив, и список значений. Отдельно выгружать значения не нужно.
Как найти индекс значения, а не элемент?
Найдите элемент через НайтиПоЗначению, затем вызовите Список.Индекс(Элемент). Второй путь — Список.ВыгрузитьЗначения().Найти(Значение).