Объединение нескольких лямбда-функций вместе с эффективным исполнением - PullRequest
3 голосов
/ 16 апреля 2009

Я пытаюсь создать динамический фильтр, используя комбинацию лямбда-функций. Но количество лямбда-функций может варьироваться в зависимости от количества различных фильтров, которые применяет пользователь.

Я хочу что-то подобное

//images is a List<ImageInfo>
var result1 = images
.Where(image => image.Bytes < 1000)
.Where(image => image.Height < 100)
.Where(image => image.Width < 100);

Где фильтр высоты применяется только к тем изображениям, которые проходят фильтр байтов. А фильтр ширины применяется только к тем изображениям, которые проходят фильтр высоты.

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

Вместо этого я создам список лямбда-функций, а затем должен применить их к списку изображений. Так что я бы закончил с чем-то вроде этого; несколько отдельных лямбда-функций.

var filter1 = images.Where(image => image.Bytes < 1000);
var filter2 = images.Where(image => image.Height < 100);
var filter3 = images.Where(image => image.Width < 100);

Как я могу объединить несколько лямбда-функций вместе, чтобы получить мой окончательный список отфильтрованных изображений?

Я сделал это

var result = filter1.Intersect<ImageInfo>(filter2).Intersect<ImageInfo>(filter3);

Но каждый фильтр просматривает основной список изображений, чтобы получить его подмножество изображений, а затем выполняет вычисление пересечений, которое отнимает слишком много ресурсов ЦП.

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

Ответы [ 4 ]

3 голосов
/ 16 апреля 2009

Хорошо, как насчет:

static Func<T, bool> CombineWithAnd<T>(IEnumerable<Func<T, bool>> filters)
{
    return x =>
    {
        foreach (var filter in filters)
        {
            if (!filter(x))
            {
                return false;
            }
        }
        return true;
    };
}

Это то, что вы ищете? В основном это захватывает набор фильтров в лямбда-выражении и применяет их один за другим (с коротким замыканием), когда используется возвращаемая лямбда. Очевидно, вы могли бы сделать то же самое в стиле «ИЛИ»:

static Func<T, bool> CombineWithOr<T>(IEnumerable<Func<T, bool>> filters)
{
    return x =>
    {
        foreach (var filter in filters)
        {
            if (filter(x))
            {
                return true;
            }
        }
        return false;
    };
}

Обратите внимание, что если набор фильтров изменяется после вызова метода (например, это List<T>, и вы добавляете новый фильтр), то возвращенный «комбинированный» фильтр будет отражать эти изменения. Если вы не хотите такого поведения, добавьте:

filters = filters.ToList();

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

1 голос
/ 16 апреля 2009

О, сладкий! Вы были очень близко, но достаточно близко, чтобы получить ответ. Чтобы применить все фильтры, return true необходимо выйти за пределы цикла foreach (показано ниже). Но да, это именно то, что я ищу.

Один вопрос или комментарий. Что на самом деле бросило меня в эту функцию, так это переменная x. Мне пришлось запустить и отладить код, чтобы выяснить, что х будет иметь тип <T>. Я никогда не видел переменную, у которой раньше не было объявлений типа или var, и это действительно меня бросало. Не могли бы вы немного объяснить правила C #, разрешающие использование переменной x без какого-либо объявления?

Кстати, очень элегантное решение.

static Func<T, bool> CombineWithAnd<T>(IEnumerable<Func<T, bool>> filters)
{
    return x =>
    {
        foreach (var filter in filters)
        {
            if (!filter(x))
            {
                return false;
            }
        }
        return true;
    };
}
0 голосов
/ 16 апреля 2009

Для чего бы это ни стоило, вам не нужно использовать цикл. (В Python, так как я не использовал C #:)

def conjoin(filter1, filter2):
    return lambda x: filter1(x) and filter2(x)

def conjoin_list(filters):
    return reduce(conjoin, filters)

(Эквивалент функции приведения в C #, которая называется сгиб там).

0 голосов
/ 16 апреля 2009

почему бы вам просто не применить функции одну за другой, как в первом примере?

Ваши фильтры имеют подпись

Func<IEnumerable<ImageInfo>, IEnumerable<ImageInfo>> 

так просто применить каждый фильтр к результату последнего?

как это?

IEnumerable<ImageInfo> filtered = images;

if(filterByBytes)
    filtered = filtered.Where(image => image.Bytes < 1000);

if(filterByHeight)
    filtered = filtered.Where(image => image.Height < 100);

if(filterByWidth)
    filtered = filtered.Where(image => image.Width < 100);

Редактировать re: comments, с макушки головы, что-то вроде ...

List<Func<IEnumerable<ImageInfo>, IEnumerable<ImageInfo>>> lambdas = new List<Func<IEnumerable<ImageInfo>, IEnumerable<ImageInfo>>>();

lambdas.add(x => x.Where(image => image.Bytes < 1000));
lambdas.add(x => x.Where(image => image.Height < 100));
lambdas.add(x => x.Where(image => image.Width < 100));

foreach(var lambda in lambdas)
    images = lamdba.Invoke(images);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...