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