Memfilter koleksi di C #

142

Saya mencari cara yang sangat cepat untuk memfilter koleksi di C #. Saat ini saya menggunakan koleksi <object> Daftar generik, tetapi saya terbuka untuk menggunakan struktur lain jika kinerjanya lebih baik.

Saat ini, saya hanya membuat Daftar baru <object> dan mengulang melalui daftar asli. Jika kriteria penyaringan cocok, saya memasukkan salinan ke daftar baru.

Apakah ada cara yang lebih baik untuk melakukan ini? Apakah ada cara untuk memfilter di tempat sehingga tidak ada daftar sementara yang diperlukan?

Jason Z
sumber
Itu akan sangat cepat. Apakah ini menyebabkan sistem Anda melambat? Apakah ini daftar yang sangat besar ? Kalau tidak, saya tidak akan khawatir.
Iain Holder

Jawaban:

237

Jika Anda menggunakan C # 3.0 Anda dapat menggunakan linq, jauh lebih baik dan lebih elegan:

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

Jika Anda tidak dapat menemukan .Where, itu berarti Anda perlu mengimpor using System.Linq;di bagian atas file Anda.

Jorge Córdoba
sumber
19
Metode ekstensi Where mengembalikan IEnumerable <T>, bukan List <T>. Seharusnya: myList.Where (x => x> 7) .ToList ()
Rafa Castaneda
1
Bagaimana cara kerjanya untuk memfilter menurut string. Seperti menemukan semua item dalam daftar string yang dimulai dengan "ch"
joncodo
2
@ JonathanO Anda dapat menggunakan metode di dalam Func. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G
1
Apakah ada cara untuk merealisasikan query LINQ? Misalnya, menggunakan .Where(predefinedQuery)alih-alih menggunakan .Where(x => x > 7)?
XenoRo
2
@AlmightyR: Cukup definisikan sebagai metode yang mengambil satu argumen. Ex: public bool predefinedQuery(int x) { return x > 7; }. Maka Anda .Where(predefinedQuery)akan bekerja dengan baik.
Don
21

Berikut ini adalah blok kode / contoh dari beberapa pemfilteran daftar menggunakan tiga metode berbeda yang saya kumpulkan untuk menunjukkan pemfilteran daftar berdasarkan Lambdas dan 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
sumber
14

List<T>memiliki FindAllmetode yang akan melakukan penyaringan untuk Anda dan mengembalikan subset dari daftar.

MSDN memiliki contoh kode yang bagus di sini: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

EDIT: Saya menulis ini sebelum saya memiliki pemahaman yang baik tentang LINQ dan Where()metodenya. Jika saya menulis ini hari ini saya mungkin akan menggunakan metode yang disebutkan Jorge di atas. The FindAllMetode masih bekerja jika Anda terjebak di NET 2.0 lingkungan sekalipun.

Mykroft
sumber
4
Linq baik-baik saja, tetapi setidaknya satu magnitude lebih lambat, jadi FindAll dan memfilter metode ekstensi (misalnya array memiliki banyak dari mereka) yang tidak bergantung pada IEnumerable masih masuk akal untuk skenario di mana kinerja penting. (FWIW, saya mendapat hasil dari faktor 7 hingga 50 lebih banyak waktu yang dibutuhkan oleh Linq dan / atau IEnumerable, umumnya)
Philm
Apakah ada alasan mengapa ini bukan jawaban yang diterima? Tampaknya lebih cepat dan sintaksinya lebih jelas (tidak ada toList ()) panggilan di akhir.
Ran Lottem
6

Anda dapat menggunakan IEnumerable untuk menghilangkan kebutuhan daftar temp.

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

di mana Cocok adalah nama metode filter Anda. Dan Anda dapat menggunakan ini seperti:

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

Ini akan memanggil fungsi GetFilteredItems bila diperlukan dan dalam beberapa kasus Anda tidak menggunakan semua item dalam koleksi yang difilter, ini mungkin memberikan beberapa peningkatan kinerja yang baik.

Serhat Ozgel
sumber
4

Untuk melakukannya di tempat, Anda dapat menggunakan metode RemoveAll dari kelas "Daftar <>" bersama dengan kelas "Predikat" khusus ... tetapi semua yang dilakukan adalah membersihkan kode ... di bawah tenda itu melakukan hal yang sama hal Anda ... tapi ya, itu di tempat, sehingga Anda melakukan hal yang sama daftar temp.

Adam Haile
sumber
4

Anda dapat menggunakan metode FindAll dari Daftar, menyediakan delegasi untuk difilter. Padahal, saya setuju dengan @ IainMH bahwa tidak perlu terlalu mengkhawatirkan diri sendiri kecuali daftar yang besar.

bdukes
sumber
3

Jika Anda menggunakan C # 3.0 Anda dapat menggunakan LINQ

Atau, jika Anda mau, gunakan sintaks kueri khusus yang disediakan oleh kompiler C # 3:

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

Menggunakan LINQ relatif jauh lebih lambat daripada menggunakan predikat yang disediakan untuk FindAllmetode daftar . Juga berhati-hatilah dengan LINQ karena penghitungan listtidak benar-benar dieksekusi sampai Anda mengakses hasilnya. Ini dapat berarti bahwa, ketika Anda merasa telah membuat daftar yang difilter, kontennya mungkin berbeda dengan yang Anda harapkan ketika Anda benar-benar membacanya.

gouldos
sumber
1

Jika daftar Anda sangat besar dan Anda memfilter berulang kali - Anda dapat mengurutkan daftar asli pada atribut filter, pencarian biner untuk menemukan titik awal dan akhir.

Waktu awal O (n * log (n)) kemudian O (log (n)).

Penyaringan standar akan mengambil O (n) setiap kali.

Daniel Roberts
sumber