Filtern von Sammlungen in C #

142

Ich suche nach einer sehr schnellen Möglichkeit, eine Sammlung in C # herauszufiltern. Ich verwende derzeit generische List <Object> -Sammlungen, bin jedoch offen für die Verwendung anderer Strukturen, wenn diese eine bessere Leistung erbringen.

Derzeit erstelle ich gerade eine neue Liste <Objekt> und durchlaufe die ursprüngliche Liste. Wenn die Filterkriterien übereinstimmen, habe ich eine Kopie in die neue Liste eingefügt.

Gibt es einen besseren Weg, dies zu tun? Gibt es eine Möglichkeit zum Filtern, sodass keine temporäre Liste erforderlich ist?

Jason Z.
quelle
Das wird unglaublich schnell gehen. Verlangsamt sich Ihr System dadurch? Ist das eine riesige Liste? Sonst würde ich mir keine Sorgen machen.
Iain Holder

Antworten:

237

Wenn Sie C # 3.0 verwenden, können Sie linq viel besser und viel eleganter verwenden:

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

Wenn Sie das nicht finden .Wherekönnen, müssen Sie es using System.Linq;oben in Ihre Datei importieren .

Jorge Córdoba
quelle
19
Die Where-Erweiterungsmethode gibt IEnumerable <T> zurück, nicht List <T>. Es sollte sein: myList.Where (x => x> 7) .ToList ()
Rafa Castaneda
1
Wie funktioniert dies beim Filtern nach Zeichenfolgen? Als würde man alle Elemente in einer Liste von Zeichenfolgen finden, die mit "ch" beginnen
joncodo
2
@JonathanO Sie können Methoden innerhalb der Func verwenden. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G
1
Gibt es eine Möglichkeit, Linq-Abfragen zu objektivieren? Zum Beispiel verwenden .Where(predefinedQuery)statt verwenden .Where(x => x > 7)?
XenoRo
2
@AlmightyR: Definieren Sie es einfach als eine Methode, die ein Argument akzeptiert. Bsp. : public bool predefinedQuery(int x) { return x > 7; }. Dann .Where(predefinedQuery)würden Sie gut funktionieren.
Don
21

Hier ist ein Codeblock / Beispiel für eine Listenfilterung mit drei verschiedenen Methoden, die ich zusammengestellt habe, um die Lambdas- und LINQ-basierte Listenfilterung zu zeigen.

#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
quelle
14

List<T>hat eine FindAllMethode, die die Filterung für Sie durchführt und eine Teilmenge der Liste zurückgibt.

MSDN hat hier ein großartiges Codebeispiel: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

EDIT: Ich habe dies geschrieben, bevor ich ein gutes Verständnis von LINQ und der Where()Methode hatte. Wenn ich das heute schreiben würde, würde ich wahrscheinlich die oben erwähnte Methode von Jorge verwenden. Die FindAllMethode funktioniert immer noch, wenn Sie in einer .NET 2.0-Umgebung stecken bleiben.

Mykroft
quelle
4
Linq ist in Ordnung, aber mindestens eine Größenordnung langsamer. Daher sind FindAll- und Filtererweiterungsmethoden (Array enthält beispielsweise eine Reihe von Methoden), die nicht auf IEnumerable basieren, für Szenarien, in denen Leistung wichtig ist, immer noch sinnvoll. (FWIW, ich habe Ergebnisse von Faktor 7 bis 50 mehr Zeit von Linq und / oder IEnumerable im Allgemeinen benötigt)
Philm
Gibt es einen Grund, warum dies nicht die akzeptierte Antwort ist? Es scheint schneller zu sein und die Syntax ist klarer (kein toList ()) Aufruf am Ende.
Ran Lottem
6

Sie können IEnumerable verwenden, um die Notwendigkeit einer temporären Liste zu beseitigen.

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

Dabei ist Matches der Name Ihrer Filtermethode. Und Sie können dies wie folgt verwenden:

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

Dadurch wird bei Bedarf die Funktion GetFilteredItems aufgerufen. In einigen Fällen, in denen Sie nicht alle Elemente in der gefilterten Sammlung verwenden, kann dies zu einem guten Leistungsgewinn führen.

Serhat Ozgel
quelle
4

Um dies zu tun, können Sie die RemoveAll-Methode der "List <>" - Klasse zusammen mit einer benutzerdefinierten "Predicate" -Klasse verwenden ... aber alles, was Sie tun müssen, ist den Code zu bereinigen ... unter der Haube macht es dasselbe was du bist ... aber ja, es macht es an Ort und Stelle, also machst du dasselbe mit der temporären Liste.

Adam Haile
quelle
4

Sie können die FindAll- Methode der Liste verwenden und einen Delegaten zum Filtern bereitstellen. Ich stimme jedoch @ IainMH zu, dass es sich nicht lohnt, sich zu viele Sorgen zu machen , es sei denn, es handelt sich um eine riesige Liste.

bdukes
quelle
3

Wenn Sie C # 3.0 verwenden, können Sie linq verwenden

Wenn Sie möchten, können Sie auch die vom C # 3-Compiler bereitgestellte spezielle Abfragesyntax verwenden:

var filteredList = from x in myList
                   where x > 7
                   select x;
Tom Lokhorst
quelle
3

Die Verwendung von LINQ ist relativ viel langsamer als die Verwendung eines Prädikats, das für die Lists- FindAllMethode bereitgestellt wird. Seien Sie auch vorsichtig mit LINQ, da die Aufzählung von listerst ausgeführt wird, wenn Sie auf das Ergebnis zugreifen. Dies kann bedeuten, dass der Inhalt, wenn Sie glauben, eine gefilterte Liste erstellt zu haben, möglicherweise von dem abweicht, was Sie beim tatsächlichen Lesen erwartet haben.

Gouldos
quelle
1

Wenn Ihre Liste sehr groß ist und Sie wiederholt filtern, können Sie die ursprüngliche Liste nach dem Filterattribut und der binären Suche sortieren, um den Start- und Endpunkt zu finden.

Anfangszeit O (n * log (n)) dann O (log (n)).

Die Standardfilterung benötigt jedes Mal O (n).

Daniel Roberts
quelle