Функция отбора элементов коллекции условием
Функция для компактного вызова отбора элементов коллекции произвольным условием.
- Описание
- Подробнее
Описание
Нередко встречаются задачи обхода коллекции с целью отобрать из нее элементы по какому то условию.
Если это табличная коллекция, то обычно у нее есть метод НайтиСтроки(Структура), позволяющий задать для каждого свойства один элемент отбора на равенство. Для более сложных отборов в таких коллекциях можно применять компоновку данных, которая добавляет ощутимые накладные расходы, но эффективно обрабатывает обращения через точку от ссылки.
Для более сложных отборов в общем случае обычно просто пишем цикл обхода коллекции и добавляем элементы в массив после проверки условия кодом.
Но все эти способы требуют писать относительно много повторяющегося кода.
В современных языках программирования для решения подобных задач есть компактные способы, например лямбда-выражения и LINQ. А у нас вроде бы пока ничего удобного появлялось. Попробуем заполнить этот пробел и напишем функцию для компактного решения подобных задач.
Ближайшее к лямбда-выражениям, что у нас есть, это метод Выполнить() для выполнения динамического кода. Нам нужно обойти коллекцию и выполнить произвольное условие-параметр для каждого элемента. Если оно вернет Истина, то добавить элемент в массив результата. Для удобства итератор цикла назовем коротким именем "Э" и предусмотрим необязательные произвольные параметры "П<N>" для использования в условии.
Но большим недостатком метода Выполнить() являются большие накладные расходы на его вызов. Поэтому вызывать его в легком цикле — плохо. Вот почему мы целиком весь цикл сделаем динамическим кодом. Тогда затратный метод Выполнить() мы будет вызывать только один раз. Но для отладки это неудобно. Поэтому предусмотрим и легкую возможность во время отладки переключиться на минимальное использование динамического кода (флаг _РежимОтладки).
// Выбирает в массив данные элементов коллекции произвольным условием.
// Параметры:
// Коллекция - Коллекция - произвольная коллекция
// Условие - Строка - логическое выражение на встроенном языке, где параметр "Э" дает доступ к элементу коллекции, а параметры "П<N>" - к параметрам условия
// ПутьКСвойству - Строка - путь к свойству, значение которого нужно извлечь из элемента коллекции и добавить в массив результата; если пусто, то добавляется сам элемент; например "ЭлементУправления.ЦветТекста"
// П1 - Произвольное - параметр "П1" для использования в условии
// П2 - Произвольное - параметр "П2" для использования в условии
// П3 - Произвольное - параметр "П3" для использования в условии
// При малом числе элементов заметно медленнее чем статический код. При 100 элементах сопоставима по скорости с ним. При большем числе элементов даже быстрее него.
// Пример: ОтобратьКоллекциюУсловиемЛкс(Коллекция, "Э<>1 И Э<>П1",, Среднее)
// Возвращаемое значение:
// Массив - элементов коллекции или значений их свойства
Функция ОтобратьКоллекциюУсловиемЛкс(Коллекция, Условие, ПутьКСвойству = "", П1 = 0, П2 = 0, П3 = 0) Экспорт
Результат = Новый Массив;
_РежимОтладки = Ложь;
Если _РежимОтладки Тогда // Можно менять на Истина в точке останова, например условием ирОбщий.ПрЛкс(_РежимОтладки, 1, 1)
// Пассивный оригинал расположенного ниже кода. Выполняйте изменения синхронно в обоих вариантах.
Для Каждого Э Из Коллекция Цикл
//Если Э <> 0 Тогда // Для сравнения скорости со статическим кодом
Если Вычислить(Условие) Тогда
Результат.Добавить(?(ПустаяСтрока(ПутьКСвойству), Э, Вычислить("Э." + ПутьКСвойству)));
КонецЕсли;
КонецЦикла;
Иначе
Попытка
// Этот вариант кода использован для ускорения. Выше расположен оригинал. Выполняйте изменения синхронно в обоих вариантах.
Выполнить("// ДинамическийКод
|Для Каждого Э Из Коллекция Цикл
| Если " + Условие + " Тогда
| Результат.Добавить(Э" + ?(ПустаяСтрока(ПутьКСвойству), "", "." + ПутьКСвойству) + ");
| КонецЕсли;
|КонецЦикла;");
Исключение
Ошибка = ОбработатьИсключениеВДинамическомКодеЛкс(ИнформацияОбОшибке());
Если ЗначениеЗаполнено(Ошибка) Тогда
ВызватьИсключение Ошибка;
Иначе
ВызватьИсключение;
КонецЕсли;
КонецПопытки;
КонецЕсли;
Возврат Результат;
КонецФункции
// Обход ошибки платформы в обычном клиентском приложении https://www.hostedredmine.com/issues/965445
// Позволяет перевыбросить исключение так, чтобы конфигуратор позволял быстрый переход к ближайшей строке статического кода из ошибки в динамическом коде.
// Динамический код должен начинаться с "// ДинамическийКод"
Функция ОбработатьИсключениеВДинамическомКодеЛкс(ИнформацияОбОшибке) Экспорт
#Если Сервер И Не Сервер Тогда
ИнформацияОбОшибке = ИнформацияОбОшибке();
#КонецЕсли
Результат = Неопределено;
Если Ложь
Или ИнформацияОбОшибке.Причина = Неопределено
Или Найти(ИнформацияОбОшибке.Описание, "Ошибка компиляции") > 0
Тогда
Ошибка = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке);
Позиция = Найти(Ошибка, "// ДинамическийКод");
Если Позиция > 0 Тогда
Результат = Лев(Ошибка, Позиция - 1);
КонецЕсли;
КонецЕсли;
Возврат Результат;
КонецФункции
Вспомогательная функция ОбработатьИсключениеВДинамическомКодеЛкс нужна чтобы обойти жестокое удаление платформой связи с исходной строкой модуля в описании ошибки в обычном приложении.
Все функции доступны в тонком клиенте.
Пример 1
Нужно отобрать из таблицы описания структуры хранения базы данных только строки описания таблиц табличных частей справочников
Сначала запишет традиционным полностью статическим кодом
Коллекция = ПолучитьСтруктуруХраненияБазыДанных();
Строки = Новый Массив;
Для Каждого Э Из Коллекция Цикл
Если Найти(Э.Метаданные, "Справочник") И Найти(Э.Метаданные, "ТабличнаяЧасть") Тогда
Строки.Добавить(Э);
КонецЕсли;
КонецЦикла;
Таблица = Коллекция.Скопировать(Строки);
А теперь запишем через нашу функцию
Коллекция = ПолучитьСтруктуруХраненияБазыДанных();
Строки = ОтобратьКоллекциюУсловиемЛкс(Коллекция, "Найти(Э.Метаданные, П1) И Найти(Э.Метаданные, П2)",, "Справочник", "ТабличнаяЧасть");
Таблица = Коллекция.Скопировать(Строки);
Компактность заметно повысилась. Но также можно отметить небольшое снижение читаемости, что для простых условий не играет большой роли.
Недостатки
К сожалению, статические анализаторы кода не смогут понять код такого условия. Тут может помочь только доработка самого встроенного языка.
Однако в процессе редактирования можно пойти на небольшую хитрость и временно делать этот код статическим. Тогда для него будут работать помощники ввода.
Рассмотрим другой пример. Тут мы выбираем из списка значений только элементы с пометкой и значением больше 3. Перед вызовом нашей функции вставим вспомогательную инструкцию для обозначения типа итератора "Э", используемого в условии. Для остальных параметров, задействованных в условии писать такие инструкции не придется, если давать им в текущем контексте те же имена, что и в условии.
Пример 2
Коллекция = Новый СписокЗначений;
Для Счетчик = 1 По 10 Цикл
Коллекция.Добавить(Счетчик,, Счетчик % 2 = 0);
КонецЦикла;
П1 = Новый Структура("Предел", 3);
#Если Сервер И Не Сервер Тогда
Э = Коллекция[0];
#КонецЕсли
ф = ОтобратьКоллекциюУсловиемЛкс(Коллекция, "Э.Пометка И Э.Значение > П1.Предел ",, П1);
Теперь при редактировании временно удаляем начальный символ "двойная кавычка" и у нас начинают работать все помощники ввода в коде условия. Забыть вернуть обратно ее мы не сможем, т.к. будет ошибка компиляции модуля из-за конечной кавычки.
Корректность нашего условия будет проверяться только в момент его выполнения, т.к. компиляция динамическая. Поэтому даже явные ошибки не будут обнаруживаться при компиляции модуля.
При малом числе элементов выполнение этой функции медленнее чем полностью статический код из-за накладных расходов на единственный вызов Выполнить(). При 100+ элементах разница уже исчезает даже для очень легких условий.
К статье приложена внешняя обработка с функцией и примерами использования.