结合多个谓词

eri*_*ric 33 c# predicate .net-2.0

在c#.NET 2.0中有什么办法!结合多个谓词?

假设我有以下代码.

List<string> names = new List<string>();
names.Add("Jacob");
names.Add("Emma");
names.Add("Michael");
names.Add("Isabella");
names.Add("Ethan");
names.Add("Emily");

List<string> filteredNames = names.FindAll(StartsWithE);

static bool StartsWithE(string s)
{
    if (s.StartsWith("E"))
    {
        return true;
    }
    else
    {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

这给了我:

Emma
Ethan
Emily
Run Code Online (Sandbox Code Playgroud)

所以这是非常酷的东西,但我知道希望能够使用多个谓词进行过滤.

所以我希望能够说出这样的话:

List<string> filteredNames = names.FindAll(StartsWithE OR StartsWithI);
Run Code Online (Sandbox Code Playgroud)

为了得到:

Emma
Isabella
Ethan
Emily
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?目前我只是过滤完整列表两次,然后将结果合并.但不幸的是,这是非常无效的,更重要的是我失去了原始的排序顺序,这在我的情况下是不可接受的.

我还需要能够迭代任意数量的过滤器/谓词,因为可能会有很多.

再次,它需要是一个.NET 2.0解决方案,遗憾的是我不能使用更新版本的框架

非常感谢.

Jon*_*eet 60

怎么样:

public static Predicate<T> Or<T>(params Predicate<T>[] predicates)
{
    return delegate (T item)
    {
        foreach (Predicate<T> predicate in predicates)
        {
            if (predicate(item))
            {
                return true;
            }
        }
        return false;
    };
}
Run Code Online (Sandbox Code Playgroud)

为了完整性:

public static Predicate<T> And<T>(params Predicate<T>[] predicates)
{
    return delegate (T item)
    {
        foreach (Predicate<T> predicate in predicates)
        {
            if (!predicate(item))
            {
                return false;
            }
        }
        return true;
    };
}
Run Code Online (Sandbox Code Playgroud)

然后用:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE, StartsWithI));
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用多播委托,然后使用它们进行拆分GetInvocationList(),然后执行相同的操作.然后你可以这样做:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE+StartsWithI));
Run Code Online (Sandbox Code Playgroud)

我不是后一种方法的忠实粉丝 - 感觉有点滥用多播.

  • 整个函数可以缩短为`return item =&gt; predicates.All(predicate =&gt; predicate(item));` (2认同)

And*_*sky 30

我想你可以这样写:

Func<string, bool> predicate1 = s => s.StartsWith("E");
Func<string, bool> predicate2 = s => s.StartsWith("I");
Func<string, bool> combinedOr = s => (predicate1(s) || predicate2(s));
Func<string, bool> combinedAnd = s => (predicate1(s) && predicate2(s));
Run Code Online (Sandbox Code Playgroud)

... 等等.


Bat*_*tle 5

我最近才想出一个与此问题类似的解决方案,这可能也会有所帮助。我将FindAll方法扩展为列表,使我可以根据需要在列表中堆叠谓词:

public static class ExtensionMethods
{
    public static List<T> FindAll<T> (this List<T> list, List<Predicate<T>> predicates)
    {
        List<T> L = new List<T> ();
        foreach (T item in list)
        {
            bool pass = true;
            foreach (Predicate<T> p in predicates)
            {
                if (!(p (item)))
                {
                    pass = false;
                    break;
                }
            }
            if (pass) L.Add (item);
        }
        return L;
    }
}
Run Code Online (Sandbox Code Playgroud)

它返回仅包含与所有给定谓词匹配的项目的列表。当然,可以很容易地将其更改为OR,而不是AND。但是,仅此一项,就可以组合出各种各样的逻辑组合。

用法:

{
    List<Predicate<int>> P = new List<Predicate<int>> ();
    P.Add (j => j > 100);
    P.Add (j => j % 5 == 0 || j % 7 == 0);
    P.Add (j => j < 1000);

    List<int> L = new List<int> () { 0, 1, 2, ... 999, 1000 }
    List<int> result = L.FindAll (P);

    // result will contain: 105, 110, 112, 115, 119, 120, ... 994, 995 
}
Run Code Online (Sandbox Code Playgroud)