В практике разработки на платформе 1С:Предприятие часто возникает задача поиска данных, которые не в точности соответствуют условию, а являются максимально приближенными к нему. Например, при анализе рентабельности нам может потребоваться найти строку, значение которой наиболее близко к 100%. Прямой поиск методом Найти() здесь не поможет, так как он работает только на точное совпадение. В этой статье мы подробно разберем несколько эффективных способов решения этой задачи.
Проанализируем самый простой и интуитивно понятный метод. Он заключается в создании временной колонки, в которой будет храниться разница (отклонение) между текущим значением и целевым числом (в нашем случае — 100). Важно вычислять именно абсолютное значение (модуль) разницы, чтобы числа 98 и 102 считались одинаково удаленными от 100.
Рассмотрим пошаговый алгоритм реализации этого метода:
Отклонение) с типом Число.Отклонение по возрастанию.Посмотрим на пример кода, реализующий этот подход:
// Предположим, ТЗ — наша исходная ТаблицаЗначений с колонкой "Рентабельность"
ТЗ.Колонки.Добавить("Отклонение", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ЦелевоеЗначение = 100;
Для Каждого СтрокаТЗ Из ТЗ Цикл
// Вычисляем модуль разницы: |Значение - 100|
СтрокаТЗ.Отклонение = Макс(СтрокаТЗ.Рентабельность - ЦелевоеЗначение, ЦелевоеЗначение - СтрокаТЗ.Рентабельность);
КонецЦикла;
// Сортируем по возрастанию отклонения
ТЗ.Сортировать("Отклонение Возр");
// Первая строка содержит самое близкое значение
Если ТЗ.Количество() > 0 Тогда
БлижайшаяСтрока = ТЗ[0];
Сообщить("Ближайшее значение: " + БлижайшаяСтрока.Рентабельность);
КонецЕсли;
Если мы не хотим изменять исходную таблицу (добавлять колонки), мы можем реализовать алгоритм поиска минимума за один проход. Выясним причину, почему это эффективно: нам не нужно тратить ресурсы на сортировку всей таблицы, достаточно лишь сравнивать отклонение текущей строки с минимальным найденным ранее.
Проанализируем пример реализации такого поиска:
ЦелевоеЗначение = 100;
МинимальноеОтклонение = Неопределено;
БлижайшаяСтрока = Неопределено;
Для Каждого ТекущаяСтрока Из ТЗ Цикл
ТекущееЗначение = ТекущаяСтрока.Рентабельность;
// Вычисляем абсолютное отклонение вручную
ТекущееОтклонение = ?(ТекущееЗначение > ЦелевоеЗначение, ТекущееЗначение - ЦелевоеЗначение, ЦелевоеЗначение - ТекущееЗначение);
Если МинимальноеОтклонение = Неопределено Или ТекущееОтклонение < МинимальноеОтклонение Тогда
МинимальноеОтклонение = ТекущееОтклонение;
БлижайшаяСтрока = ТекущаяСтрока;
КонецЕсли;
КонецЦикла;
Если БлижайшаяСтрока <> Неопределено Тогда
// Мы нашли нужную строку
КонецЕсли;
Использование запросов считается "хорошим тоном" в 1С, особенно если данных много. Даже если данные находятся в ТаблицеЗначений, мы можем поместить её во временную таблицу и выполнить поиск средствами SQL. Это позволит избежать ручного перебора строк.
Разберем, как вычислить модуль числа в запросе, так как встроенной функции ABS() в языке запросов 1С нет. Мы используем конструкцию ВЫБОР:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ТЗ КАК ТЗ;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ ПЕРВЫЕ 1
| Т.Рентабельность,
| ВЫБОР
| КОГДА Т.Рентабельность > 100 ТОГДА Т.Рентабельность - 100
| ИНАЧЕ 100 - Т.Рентабельность
| КОНЕЦ КАК Отклонение
|ИЗ
| ВТ_Данные КАК Т
|УПОРЯДОЧИТЬ ПО
| Отклонение ВОЗР";
Запрос.УстановитьПараметр("ТЗ", ВашаТаблицаЗначений);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
// Искомое значение в Выборка.Рентабельность
КонецЕсли;
При решении данной задачи важно учитывать несколько критических моментов, которые могут повлиять на корректность результата:
Рентабельность встречаются значения NULL или Неопределено, алгоритм может выдать ошибку. Перед расчетами следует проверять значения с помощью ЗначениеЗаполнено() или типизировать колонку при создании таблицы.Если поиск ближайшего значения выполняется многократно в коде (например, внутри другого цикла), рекомендуется использовать индексацию. Хотя индекс в 1С помогает при поиске на равенство, он также значительно ускоряет операцию Сортировать(). Для этого используется метод: ТЗ.Индексы.Добавить("Рентабельность");.
Подведем итог: для разовых задач на небольших таблицах идеально подходит Способ 2 (цикл), так как он не требует лишней памяти. Для сложных отчетов и больших объемов данных лучше использовать Способ 3 (запрос), который обеспечит максимальную чистоту кода и производительность.