Минимум массива во встроенном языке 1С: цикл, Мин(), сортировка и запрос

· 9 мин

Содержание

Чтобы найти минимальное значение в массиве 1С, перебирают массив в цикле Для Каждого и запоминают наименьший элемент. Встроенная функция Мин() массив целиком не принимает: она сравнивает только значения, перечисленные через запятую, поэтому внутри цикла её удобно вызывать как Минимум = Мин(Минимум, Элемент).

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

Функция Мин() в 1С: что она умеет и чего нет

Мин() — функция глобального контекста. Она принимает произвольное число параметров и возвращает наименьший из них:

Минимум = Мин(120, 85, 300);          // 85
РанняяДата = Мин(Дата1, Дата2, Дата3); // самая ранняя дата

Она работает с числами, строками и датами. Есть пара Макс() с тем же синтаксисом.

Главное ограничение — функция не разворачивает коллекцию. Если написать Мин(МассивЦен), платформа увидит один параметр-массив, а не 1 000 цен внутри него. Минимальный элемент так не получить.

Зато Мин() идеальна, когда значений немного и они известны заранее: выбрать меньшую из двух цен, ограничить скидку сверху, взять раннюю из двух дат.

// Скидка не больше 15 %
Скидка = Мин(РасчетнаяСкидка, 15);

Комбинация Мин() + цикл — самый короткий рабочий вариант для массива. Пример — в следующем разделе.

Поиск минимума в массиве перебором

Перебор — основной способ. Он проходит массив один раз, не создаёт лишних объектов и понятен любому, кто будет читать код после вас.

Вариант 1. Цикл Для Каждого

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

Стартовое значение берём из самого массива — Массив[0]. Не начинайте с «очень большого числа» вроде 999999999: однажды в данных окажется сумма больше, и функция вернёт число, которого в массиве нет.

Вариант 2. Цикл с вызовом Мин()

Тело цикла сокращается до одной строки:

Минимум = Массив[0];
Для Каждого Элемент Из Массив Цикл
	Минимум = Мин(Минимум, Элемент);
КонецЦикла;

Результат тот же. Выбирайте по вкусу: явное Если легче отлаживать, Мин() короче.

Вариант 3. Нужен не минимум, а его позиция

Если нужно знать, где лежит минимальный элемент (чтобы удалить его или взять соседний), храните индекс:

ИндексМинимума = 0;
Для Индекс = 1 По Массив.ВГраница() Цикл
	Если Массив[Индекс] < Массив[ИндексМинимума] Тогда
		ИндексМинимума = Индекс;
	КонецЕсли;
КонецЦикла;

Минимум = Массив[ИндексМинимума];

Индексы массива начинаются с нуля, ВГраница() возвращает последний индекс. Для пустого массива она вернёт −1, поэтому проверку Массив.Количество() = 0 делайте до цикла.

Если минимум уже известен, индекс даст метод Массив.Найти(Минимум). Он вернёт индекс первого совпадения или Неопределено.

Минимальное значение по условию

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

Минимум с отбором элементов

Стартовое значение здесь — Неопределено, потому что первый элемент массива может не пройти условие:

Минимум = Неопределено;
Для Каждого Элемент Из Массив Цикл
	
	Если ТипЗнч(Элемент) <> Тип("Число") Или Элемент <= 0 Тогда
		Продолжить;
	КонецЕсли;
	
	Если Минимум = Неопределено Или Элемент < Минимум Тогда
		Минимум = Элемент;
	КонецЕсли;
	
КонецЦикла;

Условие Минимум = Неопределено Или Элемент < Минимум безопасно: 1С вычисляет логические выражения по сокращённой схеме. Если первая часть истинна, сравнение с Неопределено не выполняется и ошибки не будет.

После цикла Минимум может остаться Неопределено — значит, ни один элемент не подошёл. Обработайте этот случай явно.

Минимум по полю структуры

Часто в массиве лежат структуры: поставщик, цена, срок. Ищем минимум по полю, а возвращаем весь элемент:

Лучший = Неопределено;
Для Каждого Предложение Из МассивПредложений Цикл
	Если Предложение.Срок > 5 Тогда
		Продолжить; // дольше пяти дней не устраивает
	КонецЕсли;
	Если Лучший = Неопределено Или Предложение.Цена < Лучший.Цена Тогда
		Лучший = Предложение;
	КонецЕсли;
КонецЦикла;

Так за один проход получаем и минимальную цену, и поставщика с его сроком.

Минимум и максимум за один проход

Если нужны обе границы, второй цикл не нужен:

Минимум = Массив[0];
Максимум = Массив[0];
Для Каждого Элемент Из Массив Цикл
	Если Элемент < Минимум Тогда
		Минимум = Элемент;
	ИначеЕсли Элемент > Максимум Тогда
		Максимум = Элемент;
	КонецЕсли;
КонецЦикла;

ИначеЕсли безопасен: элемент меньше текущего минимума не может одновременно быть больше максимума, ведь минимум никогда не превышает максимум.

Большие массивы: что реально ускоряет поиск

Поиск минимума в несортированном массиве требует посмотреть каждый элемент. Быстрее одного прохода алгоритма нет, это ограничение самой задачи.

Про «разделяй и властвуй»

Рекурсивное деление массива пополам часто советуют как оптимизацию. В 1С это не ускоряет поиск:

  • 🔹 сравнений остаётся столько же — около N−1;
  • 🔹 добавляются вызовы функций и создание подмассивов;
  • 🔹 встроенный язык выполняет код в одном потоке, половины массива параллельно не обработаются.

Деление имеет смысл только с фоновыми заданиями, когда каждое задание само читает свою порцию данных из базы. Если же массив уже в памяти, передача частей в задания съест весь выигрыш.

Что действительно помогает

ПриёмКогда даёт эффект
Считать МИНИМУМ() в запросе, не выгружая данные в массивДанные лежат в базе: остатки, цены, документы
Не гонять массив между клиентом и серверомМассив собирается на сервере, а минимум нужен на клиенте
Один проход вместо несколькихНужны минимум, максимум и количество одновременно
Не сортировать ради одного значенияНужен только один минимальный элемент

Самый частый реальный случай: разработчик выбирает тысячи цен запросом, складывает их в массив и ищет минимум циклом. Агрегатная функция в самом запросе сделает то же на стороне СУБД и вернёт одно число.

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	МИНИМУМ(ЦеныНоменклатуры.Цена) КАК МинимальнаяЦена
|ИЗ
|	РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
|ГДЕ
|	ЦеныНоменклатуры.Номенклатура = &Номенклатура";

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

Чтобы сравнить варианты на своих данных, засеките время через ТекущаяУниверсальнаяДатаВМиллисекундах() до и после цикла. Замер на реальном объёме надёжнее любых общих рекомендаций.

Минимум без цикла: сортировка, таблица значений, запрос

У объекта Массив нет метода сортировки. Поэтому все «бесцикловые» способы сводятся к переносу данных в коллекцию, которая умеет сортировать или агрегировать.

Через СписокЗначений

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

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

Через ТаблицаЗначений

Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Значение", Новый ОписаниеТипов("Число"));
Таблица.ЗагрузитьКолонку(Массив, "Значение");
Таблица.Сортировать("Значение Возр");
Минимум = Таблица[0].Значение;

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

Через запрос к таблице значений

Таблицу значений можно передать в запрос параметром. Использовать её можно только в запросе, который помещает данные во временную таблицу:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Т.Значение КАК Значение
|ПОМЕСТИТЬ ВТ_Значения
|ИЗ
|	&Таблица КАК Т
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	МИНИМУМ(ВТ_Значения.Значение) КАК Минимум
|ИЗ
|	ВТ_Значения КАК ВТ_Значения";
Запрос.УстановитьПараметр("Таблица", Таблица);

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
	Минимум = Выборка.Минимум;
КонецЕсли;

Для одного массива это избыточно. Способ оправдан, когда временную таблицу дальше соединяют с данными базы: например, ищут минимум по группам или сразу подтягивают реквизиты номенклатуры.

Какой способ выбрать

СпособКогда подходитМинусы
Мин(А, Б, В)2–5 известных значенийНе принимает массив
Цикл Для КаждогоПочти всегдаНужно писать 6–8 строк
СписокЗначенийНужны несколько наименьшихСортировка медленнее прохода
ТаблицаЗначенийДанные уже в таблице, нужна сортировка по колонкамПриведение типов скрывает ошибки
Запрос с МИНИМУМ()Данные в базе или нужна группировкаТребует сервера, больше кода

Практика: универсальная функция для общего модуля

Чтобы не писать цикл заново в каждой обработке, вынесите поиск в серверный общий модуль. Функция ниже ищет минимум по массиву чисел, дат или структур, пропускает незаполненные значения и возвращает и значение, и индекс.

// Возвращает структуру с полями Значение и Индекс
// или Неопределено, если подходящих элементов нет.
// ИмяПоля — имя поля структуры; пустая строка — сравнивать сами элементы.
Функция НайтиМинимум(Массив, ИмяПоля = "") Экспорт
	
	Результат = Неопределено;
	
	Для Индекс = 0 По Массив.ВГраница() Цикл
		
		Элемент = Массив[Индекс];
		Значение = ?(ПустаяСтрока(ИмяПоля), Элемент, Элемент[ИмяПоля]);
		
		Если Не ЗначениеЗаполнено(Значение) Тогда
			Продолжить;
		КонецЕсли;
		
		Если Результат = Неопределено Или Значение < Результат.Значение Тогда
			Результат = Новый Структура("Значение, Индекс", Значение, Индекс);
		КонецЕсли;
		
	КонецЦикла;
	
	Возврат Результат;
	
КонецФункции

Обращение Элемент[ИмяПоля] работает для структур и строк таблицы значений. Для пустого массива цикл не выполнится ни разу, потому что ВГраница() вернёт −1, и функция вернёт Неопределено.

Учтите: ЗначениеЗаполнено() считает ноль незаполненным. Если в ваших данных ноль — законное значение (бесплатная позиция, нулевой остаток), замените эту проверку на Значение = Неопределено.

Примеры вызова:

// Самая ранняя плановая дата поставки
Ранняя = НайтиМинимум(МассивДат);

// Самое дешёвое предложение из массива структур
Лучшее = НайтиМинимум(МассивПредложений, "Цена");
Если Лучшее <> Неопределено Тогда
	Поставщик = МассивПредложений[Лучшее.Индекс].Поставщик;
КонецЕсли;

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

Разные типы в одном массиве

Если в массиве смешались число и строка или попался Неопределено, сравнение < упадёт с ошибкой:

Операция сравнения на больше-меньше допустима только для значений совпадающих примитивных типов (Булево, Число, Строка, Дата)

Типичный источник — данные из файла, HTTP-ответа или незаполненного реквизита. Лечится проверкой ТипЗнч(Элемент) = Тип("Число") перед сравнением, как в примере с отбором выше.

Ссылки на справочники и документы тоже нельзя сравнивать на «больше-меньше». Ищите минимум по реквизиту: Элемент.Цена, а не по самой ссылке.

Числа, сохранённые строками

Строки сравниваются посимвольно. Поэтому "10" < "9" — истина, и минимумом окажется «10». Перед поиском преобразуйте значения: Число(Строка), а для сомнительных данных — с проверкой через Попытка.

Пустая дата как минимум

Пустая дата '00010101' меньше любой реальной даты. Если в массиве есть незаполненные даты, минимумом станет 1 января 0001 года. Пропускайте такие элементы условием Если Не ЗначениеЗаполнено(Элемент) Тогда Продолжить;.

Та же логика для нулей: если ноль означает «нет данных», а не «бесплатно», отбрасывайте его явно.

Пустой массив

Массив[0] на пустом массиве вызовет ошибку выхода индекса за границу. Проверяйте Массив.Количество() до первого обращения и решите, что возвращать: Неопределено, 0 или исключение. Выбор зависит от того, что ждёт вызывающий код.

Как искать минимум в вашем случае?

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

Можно ли передать массив в функцию Мин()?
Нет. Мин() сравнивает только значения, перечисленные через запятую. Для массива используйте цикл или вызывайте Мин(Минимум, Элемент) внутри цикла.
Как найти минимум в табличной части документа?
Перебирайте строки циклом Для Каждого Строка Из Объект.Товары и сравнивайте нужную колонку. Если документ записан, минимум по колонке можно получить запросом с МИНИМУМ() к табличной части.
Что вернёт поиск, если одинаковых минимумов несколько?
Значение будет одно и то же. Если нужен индекс, условие Элемент < Минимум сохранит первый встреченный, а условие Элемент <= Минимум — последний.
Как найти минимальную дату в массиве?
Тем же циклом, что и для чисел: даты сравниваются операторами < и >. Заранее отбросьте пустые даты, иначе минимумом станет 01.01.0001.
Где выполнять поиск: на клиенте или на сервере?
Там, где уже лежит массив. Если массив собран на сервере, найдите минимум там и передайте на клиент одно значение, а не всю коллекцию.