質問
C# でコレクションをフィルタリングする非常に高速な方法を探しています。現在、汎用の List<object> コレクションを使用していますが、パフォーマンスが向上する場合は、他の構造を使用することにも前向きです。
現在、新しい List<object> を作成し、元のリストをループしているだけです。フィルタリング基準が一致する場合は、コピーを新しいリストに追加します。
これを行うより良い方法はありますか?一時的なリストが不要になるように適切にフィルタリングする方法はありますか?
解決
C# 3.0 を使用している場合は、はるかに優れた、よりエレガントな linq を使用できます。
List<int> myList = GetListOfIntsFromSomewhere();
// This will filter out the list of ints that are > than 7, Where returns an
// IEnumerable<T> so a call to ToList is required to convert back to a List<T>.
List<int> filteredList = myList.Where( x => x > 7).ToList();
見つからない場合は、 .Where
, 、つまりインポートする必要があります using System.Linq;
ファイルの先頭に。
他のヒント
ここでは、Lambdas と LINQ ベースのリスト フィルタリングを示すためにまとめた 3 つの異なるメソッドを使用したリスト フィルタリングのコード ブロック/例を示します。
#region List Filtering
static void Main(string[] args)
{
ListFiltering();
Console.ReadLine();
}
private static void ListFiltering()
{
var PersonList = new List<Person>();
PersonList.Add(new Person() { Age = 23, Name = "Jon", Gender = "M" }); //Non-Constructor Object Property Initialization
PersonList.Add(new Person() { Age = 24, Name = "Jack", Gender = "M" });
PersonList.Add(new Person() { Age = 29, Name = "Billy", Gender = "M" });
PersonList.Add(new Person() { Age = 33, Name = "Bob", Gender = "M" });
PersonList.Add(new Person() { Age = 45, Name = "Frank", Gender = "M" });
PersonList.Add(new Person() { Age = 24, Name = "Anna", Gender = "F" });
PersonList.Add(new Person() { Age = 29, Name = "Sue", Gender = "F" });
PersonList.Add(new Person() { Age = 35, Name = "Sally", Gender = "F" });
PersonList.Add(new Person() { Age = 36, Name = "Jane", Gender = "F" });
PersonList.Add(new Person() { Age = 42, Name = "Jill", Gender = "F" });
//Logic: Show me all males that are less than 30 years old.
Console.WriteLine("");
//Iterative Method
Console.WriteLine("List Filter Normal Way:");
foreach (var p in PersonList)
if (p.Gender == "M" && p.Age < 30)
Console.WriteLine(p.Name + " is " + p.Age);
Console.WriteLine("");
//Lambda Filter Method
Console.WriteLine("List Filter Lambda Way");
foreach (var p in PersonList.Where(p => (p.Gender == "M" && p.Age < 30))) //.Where is an extension method
Console.WriteLine(p.Name + " is " + p.Age);
Console.WriteLine("");
//LINQ Query Method
Console.WriteLine("List Filter LINQ Way:");
foreach (var v in from p in PersonList
where p.Gender == "M" && p.Age < 30
select new { p.Name, p.Age })
Console.WriteLine(v.Name + " is " + v.Age);
}
private class Person
{
public Person() { }
public int Age { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
}
#endregion
List には、フィルタリングを実行してリストのサブセットを返す FindAll メソッドがあります。
msdn には、優れたコード例がここにあります。 http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx
編集:これは、Linq と Where() メソッドを十分に理解する前に書きました。もし私が今日これを書くとしたら、おそらくホルヘが上で述べた方法を使うでしょう。ただし、.NET 2.0 環境でスタックしている場合でも、FindAll メソッドは引き続き機能します。
IEnumerable を使用すると、一時リストの必要性を排除できます。
public IEnumerable<T> GetFilteredItems(IEnumerable<T> collection)
{
foreach (T item in collection)
if (Matches<T>(item))
{
yield return item;
}
}
ここで、Matches はフィルター メソッドの名前です。そして、これを次のように使用できます。
IEnumerable<MyType> filteredItems = GetFilteredItems(myList);
foreach (MyType item in filteredItems)
{
// do sth with your filtered items
}
これにより、必要に応じて GetFilteredItems 関数が呼び出され、フィルタリングされたコレクション内のすべての項目を使用しない場合には、パフォーマンスが向上する可能性があります。
これを適切に行うには、カスタムの「Predicate」クラスとともに「List<>」クラスの RemoveAll メソッドを使用できます。ただし、コードをクリーンアップするだけです。内部ではあなたと同じことをしています...しかし、はい、それは適切な場所で行われるので、一時リストでも同じことを行います。
Linq を使用すると、Lists FindAll メソッドに提供される述語を使用するよりもかなり遅くなります。また、結果にアクセスするまでリストの列挙は実際には実行されないため、Linq についても注意する必要があります。これは、フィルタリングされたリストを作成したと思っても、実際に読んだときに内容が期待したものと異なる可能性があることを意味します。
C# 3.0 を使用している場合は、linq を使用できます。
または、必要に応じて、C# 3 コンパイラが提供する特別なクエリ構文を使用します。
var filteredList = from x in myList
where x > 7
select x;
リストが非常に大きく、繰り返しフィルター処理を行う場合は、フィルター属性で元のリストを並べ替え、二分検索して開始点と終了点を見つけることができます。
初期時間は O(n*log(n))、その後は O(log(n))。
標準フィルタリングには毎回 O(n) かかります。