Поиск элемента в массиве на встроенном языке 1С: от Найти() до Соответствия и двоичного поиска

· 10 мин

Содержание

Найти значение в массиве 1С проще всего методом Массив.Найти(Значение): он возвращает индекс первого совпадения (счёт с нуля) или Неопределено, если такого элемента нет. Цикл для этого писать не нужно, а сравнивать результат надо именно с Неопределено, а не проверять «на истину».

Частое заблуждение — что Найти() умеет искать по полю структуры или по части строки. Не умеет: он сравнивает элемент целиком. Для поиска по условию нужен цикл, а для частых поисков по ключу массив лучше заменить Соответствием. Ниже все пять рабочих способов с кодом и с тем, когда какой выбирать.

Метод Найти(): поиск значения в массиве одной строкой

Синтаксис простой: Индекс = Массив.Найти(ИскомоеЗначение). Метод есть у Массив и у ФиксированныйМассив.

Коды = Новый Массив;
Коды.Добавить("А-100");
Коды.Добавить("Б-200");
Коды.Добавить("В-300");

Индекс = Коды.Найти("Б-200");

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

Здесь Индекс будет равен 1: первый элемент массива имеет индекс 0. Формат с ЧН=0 нужен, чтобы ноль выводился как «0», а не пустой строкой.

Как метод сравнивает значения:

  • 🔢 Числа, строки, даты, булево — по значению. Строки сравниваются с учётом регистра: "ООО Ромашка" и "ооо ромашка" — разные значения.
  • 🔗 Ссылки (СправочникСсылка, ДокументСсылка) — по значению ссылки. Если в массиве лежат ссылки на номенклатуру, Найти(СсылкаНаТовар) сработает как ожидается.
  • 📦 Структуры, массивы, объекты — по самому экземпляру, а не по содержимому. Новая структура с теми же полями найдена не будет.
  • 🧩 Типы не приводятся: число 5 и строка "5" — разные значения, Найти(5) строку не найдёт.

Если нужно просто проверить наличие, хватит одной строки: ЕстьВМассиве = (Коды.Найти("Б-200") <> Неопределено);

Найти все вхождения, а не первое

Найти() останавливается на первом совпадении. Чтобы собрать все индексы, нужен цикл (о нём следующий раздел) или повторный поиск в оставшейся части. Встроенного метода «найти все» у массива нет.

Поиск в массиве циклом: Для Каждого, Для по индексу и Пока

Цикл нужен, когда условие сложнее простого равенства: поиск без учёта регистра, по части строки, по диапазону чисел, все вхождения.

Для Каждого — когда индекс не важен

ИскомоеИмя = ВРег("иванов");
Найденный = Неопределено;

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

ВРег() приводит обе стороны к верхнему регистру, и сравнение перестаёт зависеть от того, как набрано имя. Прервать выходит из цикла сразу после первого совпадения — без него перебор пройдёт весь массив.

Для по индексу — когда нужна позиция

Индексы = Новый Массив;

Для Сч = 0 По Суммы.ВГраница() Цикл
    Если Суммы[Сч] > 10000 Тогда
        Индексы.Добавить(Сч);
    КонецЕсли;
КонецЦикла;

Так собираются все позиции элементов, подходящих под условие. Для пустого массива ВГраница() возвращает -1, и цикл не выполнится ни разу — отдельная проверка на пустоту не нужна.

Поиск по части строки делают функцией СтрНайти() (платформа 8.3.6 и выше): Если СтрНайти(Наименование, "болт") > 0 Тогда. Она тоже учитывает регистр, поэтому обе строки удобно предварительно привести через НРег().

Пока — когда условие выхода составное

Индекс = 0;
Найдено = Ложь;
Всего = Массив.Количество();

Пока Индекс < Всего И Не Найдено Цикл
    Найдено = (Массив[Индекс] = ИскомоеЗначение);
    Если Не Найдено Тогда
        Индекс = Индекс + 1;
    КонецЕсли;
КонецЦикла;

После цикла при Найдено = Истина в Индекс лежит позиция элемента. Вариант с Пока удобен, если искать надо, например, не дальше определённой позиции или до первого «пустого» элемента.

Если по ходу поиска элементы удаляются

Удаление внутри прямого цикла сдвигает индексы, и элемент, стоящий следом за удалённым, пропускается. Удалять найденное нужно с конца:

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

Индекс считается от количества, сохранённого до цикла, поэтому удаление не ломает перебор.

Как искать в массиве структур и соответствий

Типичная ситуация: из обработки или HTTP-сервиса пришёл массив структур вида {Код, Наименование, Цена}, и нужно найти строку по коду. Найти() здесь бесполезен — он сравнивает структуры как экземпляры.

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

Если у части структур поля Код может не быть, прямое обращение Элемент.Код вызовет ошибку. Тогда проверяйте поле через Свойство():

ЗначениеКода = Неопределено;
Если Элемент.Свойство("Код", ЗначениеКода) И ЗначениеКода = Код Тогда
    Возврат Элемент;
КонецЕсли;

Второй параметр Свойство() сразу получает значение поля, если оно есть.

С массивом соответствий логика та же, только обращение идёт через Получить(): Если Элемент.Получить("Код") = Код Тогда. Отсутствующий ключ вернёт Неопределено, ошибки не будет.

Поиск по нескольким полям

Если условие из двух-трёх полей и искать надо много раз, массив структур удобнее загрузить в ТаблицаЗначений и искать через НайтиСтроки():

Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Код");
Таблица.Колонки.Добавить("Склад");
Таблица.Колонки.Добавить("Цена");

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

Отбор = Новый Структура("Код, Склад", "А-100", "Основной");
НайденныеСтроки = Таблица.НайтиСтроки(Отбор);

НайтиСтроки() возвращает массив строк таблицы — пустой, если ничего не подошло. ЗаполнитьЗначенияСвойств() переносит одноимённые поля структуры в колонки строки.

Двоичный поиск в отсортированном массиве

Метод Найти() и любой цикл просматривают элементы по порядку. На массиве из 1 000 000 элементов это в среднем 500 000 сравнений. Двоичный поиск каждый раз делит диапазон пополам и укладывается в 20 сравнений, потому что 2 в 20-й степени — это 1 048 576.

Условие одно, но жёсткое: массив должен быть отсортирован по возрастанию. На неотсортированном массиве алгоритм вернёт неверный результат без всякой ошибки.

Функция ДвоичныйПоиск(Массив, Значение)
    Лево = 0;
    Право = Массив.ВГраница();

    Пока Лево <= Право Цикл
        Середина = Цел((Лево + Право) / 2);
        Текущее = Массив[Середина];

        Если Текущее = Значение Тогда
            Возврат Середина;
        ИначеЕсли Текущее < Значение Тогда
            Лево = Середина + 1;
        Иначе
            Право = Середина - 1;
        КонецЕсли;
    КонецЦикла;

    Возврат Неопределено;
КонецФункции

Прогон на массиве [3, 8, 15, 21, 40] с поиском 21: Середина = 2 (значение 15, меньше 21) → Лево = 3; Середина = Цел(7 / 2) = 3 (значение 21) → найдено, индекс 3.

Как отсортировать массив перед поиском

У самого массива метода сортировки нет. Рабочий приём — через список значений:

Список = Новый СписокЗначений;
Список.ЗагрузитьЗначения(Массив);
Список.СортироватьПоЗначению(НаправлениеСортировки.Возр);
Массив = Список.ВыгрузитьЗначения();

Операторы < и > работают для чисел, дат и строк. Для ссылок, структур и смешанных типов двоичный поиск не подходит.

Сортировка сама стоит времени. Если поиск нужен один раз, сортировать ради него нет смысла: Найти() справится быстрее. Двоичный поиск окупается, когда массив отсортирован один раз, а ищут по нему сотни и тысячи раз.

Когда массив лучше заменить другой коллекцией

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

Соответствие — быстрый поиск по ключу

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

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

Цена = Цены.Получить("Б-200");
Если Цена = Неопределено Тогда
    // кода нет
КонецЕсли;

Соответствие ещё и заменяет «множество»: чтобы быстро проверять наличие значения, кладите его ключом с Истина в значении и проверяйте Набор.Получить(Значение) = Истина. Тот же приём убирает дубли из массива без вложенного цикла. В конфигурациях на БСП для этого есть готовая функция ОбщегоНазначенияКлиентСервер.СвернутьМассив().

Структура — когда ключи известны заранее

Ключ структуры должен быть корректным идентификатором: без пробелов, не начинаться с цифры. Поэтому коды вида «А-100» ключом структуры быть не могут. Проверка наличия — Структура.Свойство("ИмяКлюча"). Структура подходит для настроек и параметров, а не для справочника из тысяч кодов.

СписокЗначений — когда нужен поиск и представление

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

ТаблицаЗначений с индексом — поиск по колонкам

Для таблицы, по которой много раз ищут по одной и той же колонке, добавьте индекс: Таблица.Индексы.Добавить("Код"). После этого Найти() и НайтиСтроки() по этой колонке работают без полного перебора строк.

Как часто и по какому признаку вы ищете?

Какой способ поиска быстрее на практике

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

СпособСколько сравнений на 1 поискКогда выбиратьПодвох
Массив.Найти()до Nразовая проверка простого значениярегистр, типы, сравнение структур по экземпляру
Цикл Для Каждогодо N, каждое на встроенном языкесложное условиемедленнее Найти() при равном числе шагов
Двоичный поискоколо log₂Nмного поисков по отсортированным числам и датамнужна сортировка, только сравнимые типы
Соответствие.Получить()не зависит от размерамного поисков по ключувремя и память на построение
ТаблицаЗначений с индексомне зависит от размерапоиск по одной или нескольким колонкаминдекс тоже нужно построить

Главная ловушка производительности — поиск внутри цикла. Если для каждого из 10 000 документов вы вызываете Найти() по массиву из 10 000 кодов, это до 100 миллионов сравнений. Соответствие, собранное один раз заранее, сводит ту же задачу к 10 000 вставкам и 10 000 обращениям.

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

Не гадайте, а измерьте на своих данных:

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

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

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

Повторите замер для каждого варианта на одинаковом массиве. Для разборов «где тормозит» удобнее замер производительности в отладчике Конфигуратора: он показывает время по каждой строке кода.

Если исходные данные лежат в базе, часто быстрее вообще не искать в массиве, а отобрать нужное запросом. Массив передаётся параметром: ГДЕ Номенклатура.Код В (&МассивКодов).

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

  • ❌ Проверка Если Индекс Тогда. Первый элемент имеет индекс 0, и условие посчитает его «не найденным». А если результат Неопределено, платформа выдаст ошибку преобразования значения к типу Булево. Правильно — Если Индекс <> Неопределено Тогда.
  • ❌ Число вместо строки. Коды, пришедшие из файла или JSON, часто строки, а ищут их числом. Приведите типы явно: Строка() или Число() перед поиском.
  • ❌ Пробелы по краям. "А-100 " и "А-100" не совпадут. Данные из загрузки прогоните через СокрЛП().
  • ❌ Дата со временем. Найти('20240115') не найдёт 15.01.2024 10:30:00. Сравнивайте через НачалоДня() в цикле.
  • ❌ Поиск структуры по содержимому. Массив.Найти(Новый Структура("Код", "А-100")) всегда вернёт Неопределено.
  • ❌ Склейка числа со строкой. "Индекс: " + Индекс для 1500 даст 1 500 с неразрывным пробелом. Используйте Формат(Индекс, "ЧГ=0").
  • ❌ Обращение за границу. Массив[Массив.Количество()] — ошибка «Индекс находится за границами диапазона». Последний элемент — Массив[Массив.ВГраница()].

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

Можно ли найти значение в массиве без учёта регистра одним методом?
Нет. Найти() сравнивает строки с учётом регистра. Либо храните значения в массиве уже в ВРег() и ищите тоже в верхнем регистре, либо перебирайте циклом.
Что вернёт Найти(), если одинаковых значений несколько?
Индекс первого из них, считая с нуля. Остальные вхождения ищутся циклом по индексу.
Работает ли Найти() у ФиксированныйМассив?
Да, метод есть и у фиксированного массива. Запрещены только изменения: Добавить(), Вставить(), Удалить().
Как найти значение в двумерном массиве?
Двумерный массив в 1С — это массив массивов. Перебирайте внешний массив и в каждой строке вызывайте Найти(); результат — пара индексов: номер строки и позиция в ней.
Как найти минимум или максимум в массиве?
Для чисел и дат подойдут функции Мин() и Макс() только с перечнем параметров, а не с массивом. Для массива пройдите цикл, запоминая текущий минимум, или отсортируйте через СписокЗначений и возьмите первый элемент.