Каталог решений - Функция отбора элементов коллекции условием

Функция отбора элементов коллекции условием

Функция отбора элементов коллекции условием

В наличии

Функция для компактного вызова отбора элементов коллекции произвольным условием.

Категория:

Описание

Нередко встречаются задачи обхода коллекции с целью отобрать из нее элементы по какому то условию.

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

Для более сложных отборов в общем случае обычно просто пишем цикл обхода коллекции и добавляем элементы в массив после проверки условия кодом.

Но все эти способы требуют писать относительно много повторяющегося кода.

В современных языках программирования для решения подобных задач есть компактные способы, например лямбда-выражения и 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+ элементах разница уже исчезает даже для очень легких условий.

К статье приложена внешняя обработка с функцией и примерами использования.

has been added to your cart:
Оформление заказа