Фильтрация коллекций в C #

147

Я ищу очень быстрый способ отфильтровать коллекцию на C #. В настоящее время я использую общие коллекции List <object>, но я открыт для использования других структур, если они работают лучше.

В настоящее время я просто создаю новый List <object> и перебираю исходный список. Если критерии фильтрации совпадают, я помещаю копию в новый список.

Есть лучший способ сделать это? Есть ли способ выполнить фильтрацию, чтобы временный список не требовался?

Джейсон Зи
источник
Это будет невероятно быстро. Это замедляет вашу систему? Это огромный список? В противном случае я бы не волновался.
Iain Holder

Ответы:

242

Если вы используете 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;в верхней части файла.

Хорхе Кордова
источник
19
Метод расширения Where возвращает IEnumerable <T>, а не List <T>. Это должно быть: myList.Where (x => x> 7) .ToList ()
Рафа Кастанеда
1
Как это работает для фильтрации по строкам. Как найти все элементы в списке строк, которые начинаются с "ch"
joncodo
2
@JonathanO Вы можете использовать методы внутри Func. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G
1
Есть ли способ объективировать запросы linq? Например, использовать .Where(predefinedQuery)вместо использования .Where(x => x > 7)?
XenoRo
2
@AlmightyR: Просто определите его как метод, который принимает один аргумент. Пример: public bool predefinedQuery(int x) { return x > 7; }. Тогда ваш .Where(predefinedQuery)будет работать нормально.
Дон
21

Вот блок кода / пример некоторой фильтрации списка с использованием трех разных методов, которые я собрал вместе, чтобы показать фильтрацию списков на основе Lambdas и 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
Джон Эриксон
источник
16

List<T>есть FindAllметод, который будет выполнять фильтрацию за вас и возвращать подмножество списка.

У MSDN есть отличный пример кода: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

РЕДАКТИРОВАТЬ: Я написал это до того, как хорошо разбирался в LINQ и Where()методе. Если бы я написал это сегодня, я бы, вероятно, использовал метод, упомянутый Хорхе выше. Однако этот FindAllметод все еще работает, если вы застряли в среде .NET 2.0.

Майкрофт
источник
4
Linq в порядке, но, по крайней мере, на одну величину медленнее, поэтому методы FindAll и расширения фильтрации (например, в массиве их много), которые не полагаются на IEnumerable, по-прежнему имеют смысл для сценариев, где важна производительность. (FWIW, я получил результаты от фактора 7 до 50 больше времени, необходимого Linq и / или IEnumerable, как правило)
Филм,
Есть ли причина, по которой это не принятый ответ? Кажется, что это быстрее, а синтаксис более четкий (без toList ()), вызов в конце.
Ран Лоттем
7

Вы можете использовать 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, когда это необходимо, и в некоторых случаях, когда вы не используете все элементы в отфильтрованной коллекции, это может обеспечить хороший прирост производительности.

Серхат Озгель
источник
4

Чтобы сделать это на месте, вы можете использовать метод RemoveAll класса «List <>» вместе с настраиваемым классом «Predicate» ... но все, что он делает, это очищает код ... под капотом он делает то же самое то, что вы ... но да, он делает это на месте, поэтому вы делаете то же самое, что и временный список.

Адам Хейл
источник
4

Вы можете использовать метод FindAll списка, предоставляя делегата для фильтрации. Хотя я согласен с @ IainMH, что не стоит слишком сильно беспокоиться, если только это не огромный список.

Bdukes
источник
3

Если вы используете C # 3.0, вы можете использовать linq

Или, если хотите, используйте специальный синтаксис запроса, предоставляемый компилятором C # 3:

var filteredList = from x in myList
                   where x > 7
                   select x;
Том Локхорст
источник
3

Использование LINQ относительно намного медленнее, чем использование предиката, предоставленного FindAllметоду Lists . Также будьте осторожны с LINQ, поскольку перечисление listфактически не выполняется, пока вы не получите доступ к результату. Это может означать, что, когда вы думаете, что создали отфильтрованный список, содержимое может отличаться от того, что вы ожидали, когда на самом деле читали его.

гульдо
источник
1

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

Начальное время O (n * log (n)), затем O (log (n)).

Стандартная фильтрация будет занимать O (n) каждый раз.

Дэниел Робертс
источник