Поиск элемента в массиве на встроенном языке 1С: от Найти() до Соответствия и двоичного поиска
Содержание
Найти значение в массиве 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"). - ❌ Обращение за границу.
Массив[Массив.Количество()]— ошибка «Индекс находится за границами диапазона». Последний элемент —Массив[Массив.ВГраница()].
Частые вопросы
Можно ли найти значение в массиве без учёта регистра одним методом?
Найти() сравнивает строки с учётом регистра. Либо храните значения в массиве уже в ВРег() и ищите тоже в верхнем регистре, либо перебирайте циклом.Что вернёт Найти(), если одинаковых значений несколько?
Работает ли Найти() у ФиксированныйМассив?
Добавить(), Вставить(), Удалить().Как найти значение в двумерном массиве?
Найти(); результат — пара индексов: номер строки и позиция в ней.Как найти минимум или максимум в массиве?
Мин() и Макс() только с перечнем параметров, а не с массивом. Для массива пройдите цикл, запоминая текущий минимум, или отсортируйте через СписокЗначений и возьмите первый элемент.