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;
}
}
これは私に与えます:
Emma
Ethan
Emily
これはかなりクールなことですが、複数の述語を使用してフィルタリングできるようにしたいと思っています。
だから私はこのようなことを言えるようになりたいです:
List<string> filteredNames = names.FindAll(StartsWithE OR StartsWithI);
取得するには:
Emma
Isabella
Ethan
Emily
どうすればこれを達成できますか?現在、私は完全なリストを2回フィルタリングし、その後結果を結合しています。しかし、残念ながらこれは非常に非効率的であり、さらに重要なことに、元の並べ替え順序が失われるため、私の状況では受け入れられません。
また、かなりの数になる可能性があるため、任意の数のフィルター/述部を反復できる必要もあります。
ここでも、.NET 2.0ソリューションである必要があります。残念ながら、新しいバージョンのフレームワークは使用できません。
どうもありがとう。
どうですか:
_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;
};
}
_
そして完全性のために:
_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;
};
}
_
次に、それを呼び出します:
_List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE, StartsWithI));
_
もう1つの方法は、マルチキャストデリゲートを使用し、GetInvocationList()
を使用してそれらを分割し、同じことを行うことです。それからあなたはすることができます:
_List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE+StartsWithI));
_
私は後者のアプローチの大ファンではありませんが、マルチキャストの乱用のように感じます。
私はあなたがこのようなものを書くことができると思います:
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));
... 等々。
最近、この問題に似た解決策が思い付きました。リストの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;
}
}
指定されたすべての述語に一致するアイテムのみのリストを返します。もちろん、ANDの代わりにORすべての述語に簡単に変更できます。しかし、それだけで、非常にさまざまな論理的な組み合わせを組み立てることができます。
使用法:
{
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
}
.NET 2.0では、そこで使用できる匿名のデリゲートがあります。
List<string> filteredNames = names.FindAll(
delegate(string s) { return StartsWithE(s) OR StartsWithI(s); }
);
実際、これを使用して関数を置き換えることもできます。
List<string> filteredNames = names.FindAll(
delegate(string s) { return s.StartsWith("E") || s.StartsWith("I"); }
);
述語メソッドをクラスにラップし、コンストラクターにテストする文字列の配列を受け入れさせることができます。
class StartsWithPredicate
{
private string[] _startStrings;
public StartsWithPredicate(params string[] startStrings)
{
_startStrings = startStrings;
}
public bool StartsWith(string s)
{
foreach (var test in _startStrings)
{
if (s.StartsWith(test))
{
return true;
}
}
return false;
}
}
その後、次のような呼び出しを行うことができます。
List<string> filtered = names.FindAll((new StartsWithPredicate("E", "I")).StartsWith);
これにより、コードベースをStartsWith
メソッドの新しいバリエーションで拡張する必要なく、入力文字列の任意の組み合わせをテストできます。
このパターンを上記の「params」配列メソッドで広範囲に使用したため、マルチキャストデリゲートについて最近知りました。デリゲートは本質的にリスト(またはマルチキャスト)をサポートしているため、params []パターンをスキップして、単一のデリゲートをTest()関数に提供するだけで済みます。提供されたPredicate <>でGetInvokationListを呼び出す必要があります。これを見てください Funcタイプのマルチキャストデリゲート(戻り値付き)?
結果を内部ORする3番目の述語を作成できます。ラムダ式を使用して、オンザフライでこれを実行できると思います。このようなもの(私はそのsnytaxがあまり上手ではないので、これはラムダ式ではありません):
static bool StartsWithEorI(string s)
{
return StartsWithE(s) || StartsWithI(s);
}