题
我正在寻找一种非常快速的方法来过滤 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;
在你的文件的顶部。
其他提示
下面是使用三种不同方法进行列表过滤的代码块/示例,我将这三种方法放在一起来展示基于 Lambda 和 LINQ 的列表过滤。
#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 函数,并且在某些情况下您不使用过滤集合中的所有项目,它可能会提供一些良好的性能增益。
要就地执行此操作,您可以使用“List <>”类的RemoveAll方法以及自定义“Predicate”类...但所做的只是清理代码...在幕后,它正在做与您相同的事情......但是,是的,它是就地执行的,所以您对临时列表执行相同的操作。
使用 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)。