Filtraggio delle raccolte in C #


142

Sto cercando un modo molto veloce per filtrare una raccolta in C #. Attualmente sto utilizzando raccolte List <object> generiche, ma sono aperto all'utilizzo di altre strutture se funzionano meglio.

Attualmente, sto solo creando un nuovo elenco <oggetto> e passando in rassegna l'elenco originale. Se i criteri di filtro corrispondono, inserisco una copia nel nuovo elenco.

C'è un modo migliore per farlo? Esiste un modo per filtrare in modo che non sia richiesto un elenco temporaneo?


Sarà velocissimo. Sta causando il rallentamento del sistema? È una lista enorme ? Altrimenti, non mi preoccuperei.
Titolare Iain,

Risposte:


237

Se stai usando C # 3.0 puoi usare linq, molto meglio e molto più elegante:

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

Se non riesci a trovare .Where, significa che devi importare using System.Linq;nella parte superiore del file.


19
Il metodo di estensione Where restituisce IEnumerable <T>, non Elenco <T>. Dovrebbe essere: myList.Where (x => x> 7). ToList ()
Rafa Castaneda,

1
Come funziona per filtrare per stringhe. Come trovare tutti gli elementi in un elenco di stringhe che iniziano con "ch"
joncodo,

2
@JonathanO È possibile utilizzare metodi all'interno di Func. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G,

1
C'è un modo per oggettivare le query linq? Ad esempio, da usare .Where(predefinedQuery)invece di usare .Where(x => x > 7)?
XenoRo,

2
@AlmightyR: basta definirlo come un metodo che accetta un argomento. Es: public bool predefinedQuery(int x) { return x > 7; }. Quindi il tuo .Where(predefinedQuery)funzionerebbe bene.
Don

21

Ecco un blocco di codice / esempio di alcuni filtri di elenco che utilizzano tre diversi metodi che ho messo insieme per mostrare i filtri di elenco basati su Lambdas e 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

14

List<T> ha un FindAll metodo che farà il filtraggio per te e restituirà un sottoinsieme dell'elenco.

MSDN ha un ottimo esempio di codice qui: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

EDIT: ho scritto questo prima di avere una buona conoscenza di LINQ e del Where()metodo. Se dovessi scrivere questo oggi probabilmente userei il metodo menzionato da Jorge sopra. Il FindAllmetodo funziona comunque se sei bloccato in un ambiente .NET 2.0.


4
Linq va bene, ma almeno una magnitudine è più lenta, quindi FindAll e i metodi di estensione del filtro (l'array ne ha un sacco, ad esempio) che non si basano su IEnumerable hanno ancora senso per gli scenari in cui le prestazioni sono importanti. (FWIW, ho ottenuto risultati dal fattore 7 al 50 in più necessari a Linq e / o IEnumerable, in generale)
Philm

C'è un motivo per cui questa non è la risposta accettata? Sembra essere più veloce e la sintassi è più chiara (nessuna chiamata a ToList ()) alla fine.
Ha funzionato Lottem il

6

È possibile utilizzare IEnumerable per eliminare la necessità di un elenco temporaneo.

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

dove Matches è il nome del tuo metodo di filtro. E puoi usarlo come:

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

Questo chiamerà la funzione GetFilteredItems quando necessario e in alcuni casi che non si utilizzano tutti gli elementi nella raccolta filtrata, potrebbe fornire un buon guadagno in termini di prestazioni.


4

Per farlo sul posto, puoi usare il metodo RemoveAll della classe "List <>" insieme a una classe "Predicate" personalizzata ... ma tutto ciò che fa è ripulire il codice ... sotto il cofano sta facendo lo stesso cosa sei ... ma sì, lo fa sul posto, quindi fai lo stesso con l'elenco delle temp.


4

È possibile utilizzare il metodo FindAll dell'elenco, fornendo un delegato su cui filtrare. Tuttavia , sono d'accordo con @ IainMH che non vale la pena preoccuparsi troppo se non è un elenco enorme.


3

Se stai usando C # 3.0 puoi usare linq

Oppure, se preferisci, usa la sintassi di query speciale fornita dal compilatore C # 3:

var filteredList = from x in myList
                   where x > 7
                   select x;

3

L'uso di LINQ è relativamente più lento rispetto all'utilizzo di un predicato fornito con il FindAllmetodo Elenchi . Prestare attenzione anche a LINQ poiché l'enumerazione di listnon viene effettivamente eseguita fino a quando non si accede al risultato. Ciò può significare che, quando pensi di aver creato un elenco filtrato, il contenuto potrebbe differire da quello che ti aspettavi quando lo hai letto.


1

Se la tua lista è molto grande e stai filtrando ripetutamente - puoi ordinare l'elenco originale sull'attributo filter, ricerca binaria per trovare i punti di inizio e fine.

Ora iniziale O (n * log (n)) quindi O (log (n)).

Il filtraggio standard richiederà O (n) ogni volta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.