Поиск значения в массиве на встроенном языке 1С: от Найти() до хеш-индекса
Содержание
Найти значение в массиве 1С проще всего методом Массив.Найти(Значение): он возвращает индекс первого совпадения или Неопределено, если значения нет. Это работает для чисел, строк, дат, булевых значений и ссылок. Для поиска по полю структуры, по условию «больше/меньше» или по подстроке нужен цикл, а для частого поиска в больших массивах — Соответствие.
Типичная ситуация: обработка загрузила 8 000 строк из Excel, и для каждой нужно понять, есть ли такой ИНН в массиве уже найденных контрагентов. Вызов Найти() внутри цикла здесь превращается в 64 млн сравнений, и форма «висит» минуту. Ниже разобраны все способы поиска в массиве и когда какой брать.
Метод Найти(): поиск значения в массиве без цикла
Найти() — встроенный метод объектов Массив и ФиксированныйМассив. Он перебирает элементы с нулевого индекса и останавливается на первом совпадении.
Коды = Новый Массив;
Коды.Добавить("00-001");
Коды.Добавить("00-002");
Коды.Добавить("00-003");
Индекс = Коды.Найти("00-002");
Если Индекс <> Неопределено Тогда
Сообщить(СтрШаблон("Найден, индекс %1", Индекс)); // индекс 1
Иначе
Сообщить("Не найден");
КонецЕсли;
Как ведёт себя метод:
- 🔢 возвращает число — индекс первого найденного элемента (нумерация с нуля);
- ❌ возвращает
Неопределено, если значения нет; - 🔁 при дублях находит только первый, остальные — только циклом;
- 🔤 строки сравнивает с учётом регистра:
"ООО Ромашка"и"ооо ромашка"— разные значения.
Проверяйте результат через <> Неопределено. Индекс 0 — законный результат: первый элемент массива. Код, который считает ноль признаком «не найдено», молча теряет первый элемент.
Что сравнивается «по значению», а что нет
| Тип элемента | Как сравнивает Найти() | Найдётся ли «такое же» значение |
|---|---|---|
| Число, Строка, Дата, Булево | по значению | да |
| Ссылка на справочник, документ | по ссылке | да, если это тот же объект базы |
| Перечисление | по значению перечисления | да |
| Структура, Массив, Соответствие | по экземпляру объекта | нет, только та же самая переменная |
| ТаблицаЗначений, строка ТЗ | по экземпляру | нет |
Последние строки таблицы — частая ловушка. Две структуры с одинаковыми полями для платформы разные объекты. Найти(Новый Структура("Код", "001")) вернёт Неопределено, даже если в массиве лежит структура с тем же кодом.
Проверка «есть ли значение в массиве»
Отдельного метода Содержит() у массива нет. Проверку пишут через Найти():
Если Коды.Найти(НовыйКод) = Неопределено Тогда
Коды.Добавить(НовыйКод);
КонецЕсли;
Так собирают массив без дублей. Для больших объёмов этот приём медленный — почему, разобрано в разделе про соответствие.
Поиск в массиве циклом с условием
Цикл нужен, когда простого равенства мало:
- 🔍 найти все вхождения, а не первое;
- 📏 условие сравнения: сумма больше 10 000, дата в периоде;
- 🔤 поиск без учёта регистра или по части строки;
- 🧩 поиск по полю вложенного объекта.
Все индексы совпадений
Функция ИндексыЗначения(Массив, Значение)
Результат = Новый Массив;
Для Индекс = 0 По Массив.ВГраница() Цикл
Если Массив[Индекс] = Значение Тогда
Результат.Добавить(Индекс);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
ВГраница() возвращает последний индекс: для массива из 5 элементов это 4. Для пустого массива — -1, и цикл Для 0 По -1 не выполнится ни разу, отдельная проверка не нужна.
Поиск без учёта регистра и по подстроке
Искомое = НРег(СокрЛП(СтрокаПоиска));
Для Каждого Наименование Из Наименования Цикл
Если СтрНайти(НРег(Наименование), Искомое) > 0 Тогда
Сообщить(Наименование);
КонецЕсли;
КонецЦикла;
СтрНайти() возвращает позицию подстроки или 0. Приводите обе строки к одному регистру через НРег() или ВРег(), иначе «Ромашка» не найдётся по запросу «ромашка».
Ранний выход из цикла
Если нужно только первое совпадение по условию, прерывайте цикл через Прервать. Без него цикл дойдёт до конца массива, даже если ответ найден на третьем элементе.
Найденный = Неопределено;
Для Каждого Сумма Из Суммы Цикл
Если Сумма > 10000 Тогда
Найденный = Сумма;
Прервать;
КонецЕсли;
КонецЦикла;
Как найти элемент массива структур по значению поля
Массив структур — частый формат: так возвращают данные HTTP-сервисы после разбора JSON, так передают строки с клиента на сервер. Найти() здесь бесполезен (см. таблицу выше), поэтому ищут циклом по полю.
Функция НайтиПоПолю(МассивСтруктур, ИмяПоля, Значение)
Для Каждого Элемент Из МассивСтруктур Цикл
Если Элемент.Свойство(ИмяПоля) И Элемент[ИмяПоля] = Значение Тогда
Возврат Элемент;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
// Вызов
Товар = НайтиПоПолю(Товары, "Артикул", "А-1520");
Зачем Свойство(): если у какой-то структуры нет поля Артикул, обращение Элемент.Артикул вызовет ошибку «Поле объекта не обнаружено». В массивах из JSON поля часто приходят не во всех элементах. Если структура гарантированно однородная, проверку можно убрать.
Поиск по нескольким полям
Условие собирают через И:
Для Каждого Строка Из Строки Цикл
Если Строка.Склад = Склад И Строка.Номенклатура = Номенклатура Тогда
// нашли
КонецЕсли;
КонецЦикла;
Когда лучше перейти на таблицу значений
На сервере массив структур удобно превратить в ТаблицаЗначений. У неё есть готовые методы поиска:
| Метод | Что возвращает |
|---|---|
Найти(Значение, "Колонка1, Колонка2") | первую строку с таким значением в одной из колонок или Неопределено |
НайтиСтроки(Новый Структура("Склад, Номенклатура", С, Н)) | массив всех строк по отбору (пустой, если ничего нет) |
Для повторного поиска по одним и тем же колонкам добавьте индекс: ТЗ.Индексы.Добавить("Склад, Номенклатура"). С индексом НайтиСтроки() работает заметно быстрее на десятках тысяч строк.
ТаблицаЗначений недоступна на клиенте. В клиентском модуле формы работайте с массивом или соответствием, а таблицу собирайте в серверной процедуре.
Фильтрация массива запросом
Запрос не умеет читать массив как таблицу-источник. Есть два рабочих сценария.
1. Массив как список значений для условия В
Это самый частый случай: есть массив ссылок, нужно выбрать из базы только их или, наоборот, исключить.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка,
| Контрагенты.ИНН
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН В (&СписокИНН)";
Запрос.УстановитьПараметр("СписокИНН", МассивИНН);
Выборка = Запрос.Выполнить().Выбрать();
Параметр для В (&...) принимает Массив, ФиксированныйМассив и СписокЗначений. Для обратного условия пишут НЕ Контрагенты.ИНН В (&СписокИНН).
2. Массив как временная таблица
Когда нужно отфильтровать сам массив условиями запроса, соединить его с данными базы или сгруппировать, массив сначала кладут в таблицу значений с типизированной колонкой:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("ИНН", Новый ОписаниеТипов("Строка",, Новый КвалификаторыСтроки(12)));
ТЗ.ЗагрузитьКолонку(МассивИНН, "ИНН");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.ИНН
|ПОМЕСТИТЬ ВТ_ИНН
|ИЗ
| &ТЗ КАК Т
|;
|ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВТ_ИНН.ИНН
|ИЗ
| ВТ_ИНН КАК ВТ_ИНН
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК К
| ПО ВТ_ИНН.ИНН = К.ИНН
|ГДЕ
| К.Ссылка ЕСТЬ NULL";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
НовыеИНН = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("ИНН");
Результат — массив ИНН, которых ещё нет в справочнике. Двумя циклами это заняло бы втрое больше кода.
Колонка таблицы значений для запроса должна иметь тип. Колонка без ОписаниеТипов даст ошибку при выполнении запроса. Код с запросом работает только на сервере.
Не тащите запрос туда, где хватает цикла: для массива из 50 строк обращение к СУБД дороже простого перебора.
Ускорение поиска: Соответствие вместо Найти()
Найти() и цикл проверяют элементы подряд. Один поиск в массиве из 10 000 элементов — до 10 000 сравнений. Если поиск стоит внутри другого цикла на 10 000 итераций, получается до 100 млн сравнений.
Соответствие хранит пары «ключ — значение» и находит ключ без перебора, по хешу. Время поиска почти не зависит от размера коллекции.
Как переделать проверку наличия
Было:
Для Каждого Строка Из СтрокиФайла Цикл
Если Обработанные.Найти(Строка.ИНН) = Неопределено Тогда
Обработанные.Добавить(Строка.ИНН);
// обработка
КонецЕсли;
КонецЦикла;
Стало:
Обработанные = Новый Соответствие;
Для Каждого Строка Из СтрокиФайла Цикл
Если Обработанные[Строка.ИНН] = Неопределено Тогда
Обработанные.Вставить(Строка.ИНН, Истина);
// обработка
КонецЕсли;
КонецЦикла;
Обращение Соответствие[Ключ] возвращает Неопределено, если ключа нет, ошибки не будет. То же делает метод Получить(Ключ).
Индекс по полю для массива структур
Если в массиве структур нужно многократно искать по артикулу, постройте индекс один раз:
ПоАртикулу = Новый Соответствие;
Для Каждого Товар Из Товары Цикл
ПоАртикулу.Вставить(Товар.Артикул, Товар);
КонецЦикла;
// Дальше — мгновенный поиск
Товар = ПоАртикулу["А-1520"];
При дублях артикула Вставить() перезапишет значение, и в индексе останется последний элемент. Если нужны все, храните в значении массив элементов.
Какой способ выбрать
| Способ | Скорость одного поиска | Когда брать |
|---|---|---|
Массив.Найти() | перебор | разовый поиск простого значения |
| Цикл с условием | перебор | условие сложнее равенства, все вхождения, поля структур |
ТЗ.НайтиСтроки() с индексом | быстро | сервер, отбор по нескольким колонкам |
| Запрос | зависит от СУБД | нужно соединить массив с данными базы |
Соответствие | почти мгновенно | много поисков по одному ключу |
Массив.Найти() и проверка результата на Неопределено.Для Каждого с Прервать после первого совпадения.Соответствие по ключу, дальше ищите через Соответствие[Ключ].В (&Список) или во временную таблицу через таблицу значений.Бинарный поиск в отсортированном массиве
Если массив уже отсортирован и меняется редко, поиск делением пополам находит значение за 14 сравнений в массиве из 10 000 элементов. Встроенного бинарного поиска у массива нет, пишут вручную:
Функция БинарныйПоиск(Массив, Значение)
Лев = 0;
Прав = Массив.ВГраница();
Пока Лев <= Прав Цикл
Середина = Цел((Лев + Прав) / 2);
Текущее = Массив[Середина];
Если Текущее = Значение Тогда
Возврат Середина;
ИначеЕсли Текущее < Значение Тогда
Лев = Середина + 1;
Иначе
Прав = Середина - 1;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
У массива нет метода сортировки. Отсортировать его можно через список значений:
Список = Новый СписокЗначений;
Список.ЗагрузитьЗначения(Массив);
Список.СортироватьПоЗначению(НаправлениеСортировки.Возр);
Массив = Список.ВыгрузитьЗначения();
На практике соответствие проще и не требует сортировки. Бинарный поиск оправдан, когда нужен поиск по диапазону: «первый элемент не меньше заданного».
Ошибки при поиске в массиве и как их избежать
Удаление найденных элементов
Если элементы удаляют в том же цикле, где ищут, идите с конца. При прямом проходе после Удалить() индексы сдвигаются, и следующий элемент пропускается.
Количество = Массив.Количество();
Для Сч = 1 По Количество Цикл
Индекс = Количество - Сч;
Если Массив[Индекс] = УдаляемоеЗначение Тогда
Массив.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Граница Количество фиксируется до цикла, а индексы идут от последнего к нулевому. Удаление элемента с индексом Индекс не затрагивает ещё не проверенные элементы с меньшими номерами.
ФиксированныйМассив
ФиксированныйМассив поддерживает Найти(), Количество() и ВГраница(), но не Добавить() и Удалить(). Такие массивы приходят из реквизитов формы и параметров сеанса. Для изменения создайте обычный массив: Новый Массив(ФиксированныйМассив).
Частые вопросы
Можно ли найти в массиве значение типа Неопределено?
Массив.Найти(Неопределено) вернёт индекс первого элемента со значением Неопределено. Так удобно искать пустые места в массиве, созданном через Новый Массив(10): все его элементы изначально равны Неопределено.Как найти максимальное значение в массиве?
Макс(), но только перечислив их явно, массив целиком функция не принимает.Чем Найти() у массива отличается от Найти() у таблицы значений?
Работает ли поиск в массиве на клиенте?
Массив и Соответствие доступны и на клиенте, и на сервере. Запрос и таблица значений — только на сервере.Как убрать дубли из массива?
ОбщегоНазначенияКлиентСервер.СвернутьМассив().