Filtrowanie kolekcji w C #

142

Szukam bardzo szybkiego sposobu na odfiltrowanie kolekcji w C #. Obecnie używam ogólnych kolekcji List <object>, ale jestem otwarty na używanie innych struktur, jeśli działają lepiej.

Obecnie właśnie tworzę nowy List <object> i przeglądam oryginalną listę. Jeśli kryteria filtrowania są zgodne, umieszczam kopię na nowej liście.

Czy jest lepszy sposób na zrobienie tego? Czy istnieje sposób na filtrowanie, aby nie było potrzeby tworzenia tymczasowej listy?

Jason Z
źródło
To będzie niesamowicie szybkie. Czy to powoduje spowolnienie systemu? Czy lista jest ogromna ? W przeciwnym razie nie martwiłbym się.
Iain Holder

Odpowiedzi:

237

Jeśli używasz C # 3.0, możesz użyć linq, o wiele lepiej i bardziej elegancko:

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();

Jeśli nie możesz znaleźć .Where, oznacza to, że musisz importować using System.Linq;na początku pliku.

Jorge Córdoba
źródło
19
Metoda rozszerzenia Where zwraca IEnumerable <T>, a nie List <T>. Powinno to być: myList.Where (x => x> 7) .ToList ()
Rafa Castaneda
1
Jak to działa w przypadku filtrowania według łańcuchów. Podobnie jak znajdowanie wszystkich pozycji na liście ciągów zaczynających się od „ch”
joncodo
2
@JonathanO Możesz używać metod wewnątrz Func. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G
1
Czy istnieje sposób na zobiektywizowanie zapytań linq? Na przykład, aby użyć .Where(predefinedQuery)zamiast używać .Where(x => x > 7)?
XenoRo
2
@AlmightyR: Po prostu zdefiniuj to jako metodę, która przyjmuje jeden argument. Np public bool predefinedQuery(int x) { return x > 7; }. : . Wtedy .Where(predefinedQuery)będzie działać dobrze.
Don
21

Oto blok kodu / przykład filtrowania list przy użyciu trzech różnych metod, które połączyłem, aby pokazać filtrowanie list oparte na Lambdzie i 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
Jon Erickson
źródło
14

List<T>ma FindAllmetodę, która wykona filtrowanie za Ciebie i zwróci podzbiór listy.

MSDN ma tutaj świetny przykład kodu: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

EDYCJA: napisałem to, zanim dobrze zrozumiałem LINQ i Where()metodę. Gdybym miał dziś to napisać, prawdopodobnie użyłbym metody, o której wspomniał Jorge powyżej. Ta FindAllmetoda nadal działa, jeśli utkniesz w środowisku .NET 2.0.

Mykroft
źródło
4
Linq jest w porządku, ale przynajmniej o jedną wielkość wolniej, więc FindAll i metody rozszerzające filtrujące (na przykład tablica ma ich kilka), które nie polegają na IEnumerable, nadal mają sens w scenariuszach, w których liczy się wydajność. (FWIW, otrzymałem wyniki od 7 do 50 więcej czasu potrzebnego Linq i / lub IEnumerable, ogólnie)
Philm
Czy istnieje powód, dla którego to nie jest akceptowana odpowiedź? Wydaje się, że jest szybszy, a składnia jest jaśniejsza (brak wywołania toList ()) na końcu.
Pobiegł Lottem
6

Możesz użyć IEnumerable, aby wyeliminować potrzebę listy tymczasowej.

public IEnumerable<T> GetFilteredItems(IEnumerable<T> collection)
{
    foreach (T item in collection)
    if (Matches<T>(item))
    {
        yield return item;
    }
}

gdzie Dopasowania to nazwa metody filtrującej. Możesz użyć tego na przykład:

IEnumerable<MyType> filteredItems = GetFilteredItems(myList);
foreach (MyType item in filteredItems)
{
    // do sth with your filtered items
}

Spowoduje to wywołanie funkcji GetFilteredItems w razie potrzeby, aw niektórych przypadkach, gdy nie używasz wszystkich elementów w filtrowanej kolekcji, może to zapewnić dobry wzrost wydajności.

Serhat Ozgel
źródło
4

Aby to zrobić na miejscu, możesz użyć metody RemoveAll klasy „List <>” wraz z niestandardową klasą „Predicate” ... ale wszystko, co robi, to wyczyszczenie kodu ... pod maską robi to samo rzecz, którą jesteś ... ale tak, robi to na miejscu, więc robisz to samo na liście tymczasowej.

Adam Haile
źródło
4

Możesz użyć metody FindAll listy, zapewniając delegata do filtrowania. Chociaż zgadzam się z @ IainMH , że nie warto się zbytnio martwić, chyba że jest to ogromna lista.

bdukes
źródło
3

Jeśli używasz języka C # 3.0, możesz użyć linq

Lub, jeśli wolisz, użyj specjalnej składni zapytania dostarczonej przez kompilator C # 3:

var filteredList = from x in myList
                   where x > 7
                   select x;
Tom Lokhorst
źródło
3

Korzystanie z LINQ jest stosunkowo wolniejsze niż użycie predykatu dostarczonego do FindAllmetody Lists . Zachowaj również ostrożność w przypadku LINQ, ponieważ wyliczenie listnie jest faktycznie wykonywane, dopóki nie uzyskasz dostępu do wyniku. Może to oznaczać, że jeśli myślisz, że utworzyłeś przefiltrowaną listę, zawartość może różnić się od tego, czego się spodziewałeś, kiedy faktycznie ją czytasz.

gouldos
źródło
1

Jeśli twoja lista jest bardzo duża i wielokrotnie filtrujesz - możesz posortować oryginalną listę według atrybutu filtru, wyszukiwanie binarne, aby znaleźć punkty początkowe i końcowe.

Czas początkowy O (n * log (n)), a następnie O (log (n)).

Standardowe filtrowanie za każdym razem zajmie O (n).

Daniel Roberts
źródło