Menggunakan LINQ untuk menghapus elemen dari Daftar <T>

655

Katakanlah saya memiliki permintaan LINQ seperti:

var authors = from x in authorsList
              where x.firstname == "Bob"
              select x;

Mengingat itu authorsListbertipe List<Author>, bagaimana saya bisa menghapus Authorelemen dari authorsListyang dikembalikan oleh permintaan ke authors?

Atau, dengan kata lain, bagaimana saya bisa menghapus semua nama sam yang menyamai Bob authorsList?

Catatan: Ini adalah contoh sederhana untuk keperluan pertanyaan.

TK.
sumber

Jawaban:

1139

Yah, akan lebih mudah untuk mengecualikan mereka di tempat pertama:

authorsList = authorsList.Where(x => x.FirstName != "Bob").ToList();

Namun, itu hanya akan mengubah nilai authorsListalih-alih menghapus penulis dari koleksi sebelumnya. Atau, Anda dapat menggunakan RemoveAll:

authorsList.RemoveAll(x => x.FirstName == "Bob");

Jika Anda benar-benar perlu melakukannya berdasarkan koleksi lain, saya akan menggunakan HashSet, RemoveAll dan Contains:

var setToRemove = new HashSet<Author>(authors);
authorsList.RemoveAll(x => setToRemove.Contains(x));
Jon Skeet
sumber
14
Apa alasan menggunakan HashSet untuk koleksi lain?
123 456 789 0
54
@ LeoLuis: Ini membuat Containspemeriksaan cepat, dan memastikan Anda hanya mengevaluasi urutan sekali.
Jon Skeet
2
@ LeoLuis: Ya, membangun HashSet dari urutan hanya mengevaluasinya sekali. Tidak yakin apa yang Anda maksud dengan "set koleksi lemah".
Jon Skeet
2
@ AndréChristofferAndersen: Apa yang Anda maksud dengan "ketinggalan jaman"? Itu masih berfungsi. Jika Anda punya List<T>, tidak apa-apa untuk menggunakannya.
Jon Skeet
4
@ AndréChristofferAndersen: Akan lebih baik menggunakanauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Jon Skeet
133

Akan lebih baik menggunakan Daftar <T> .RemoveAll untuk melakukannya.

authorsList.RemoveAll((x) => x.firstname == "Bob");
Reed Copsey
sumber
8
@Reed Copsey: Parameter lambda dalam contoh Anda terlampir dalam tanda kurung, yaitu, (x). Apakah ada alasan teknis untuk ini? Apakah ini dianggap praktik yang baik?
Matt Davis,
24
Tidak. Diperlukan dengan> 1 parameter. Dengan satu parameter, ini opsional, tetapi itu membantu menjaga konsistensi.
Reed Copsey
48

Jika Anda benar-benar perlu menghapus item, lalu bagaimana dengan Kecuali ()?
Anda dapat menghapus berdasarkan daftar baru, atau menghapus on-the-fly dengan membuat Linq.

var authorsList = new List<Author>()
{
    new Author{ Firstname = "Bob", Lastname = "Smith" },
    new Author{ Firstname = "Fred", Lastname = "Jones" },
    new Author{ Firstname = "Brian", Lastname = "Brains" },
    new Author{ Firstname = "Billy", Lastname = "TheKid" }
};

var authors = authorsList.Where(a => a.Firstname == "Bob");
authorsList = authorsList.Except(authors).ToList();
authorsList = authorsList.Except(authorsList.Where(a=>a.Firstname=="Billy")).ToList();
BlueChippy
sumber
Except()adalah satu-satunya cara untuk pergi di tengah-tengah pernyataan LINQ. IEnumerabletidak punya Remove()atau tidak RemoveAll().
Jari Turkia
29

Anda tidak dapat melakukan ini dengan operator LINQ standar karena LINQ menyediakan permintaan, bukan memperbarui dukungan.

Tetapi Anda dapat membuat daftar baru dan mengganti yang lama.

var authorsList = GetAuthorList();

authorsList = authorsList.Where(a => a.FirstName != "Bob").ToList();

Atau Anda dapat menghapus semua item dalam authorspass kedua.

var authorsList = GetAuthorList();

var authors = authorsList.Where(a => a.FirstName == "Bob").ToList();

foreach (var author in authors)
{
    authorList.Remove(author);
}
Daniel Brückner
sumber
12
RemoveAll()bukan operator LINQ.
Daniel Brückner
Permintaan maaf saya. Anda 100% benar. Sayangnya, saya tidak bisa membalikkan downvote saya. Maaf soal itu.
Shai Cohen
Removejuga merupakan metode List< T>, bukan metode System.Linq.Enumerable .
DavidRR
@Aniel, Perbaiki saya jika saya salah kita dapat menghindari. Daftar () dari mana condtion untuk opsi kedua. Yakni kode di bawah ini akan berfungsi. var penulisList = GetAuthorList (); penulis var = authorList.Where (a => a.FirstName == "Bob"); foreach (penulis var di penulis) {authorList.Remove (penulis); }
Sai
Ya, ini akan berhasil. Mengubahnya menjadi daftar hanya diperlukan jika Anda membutuhkan daftar untuk meneruskannya ke beberapa metode atau jika Anda ingin menambah atau menghapus lebih banyak barang nanti. Mungkin juga berguna jika Anda harus menghitung urutan beberapa kali karena Anda hanya perlu mengevaluasi kondisi berpotensi mahal sekali atau jika hasilnya dapat berubah antara dua pencacahan, misalnya karena kondisinya tergantung pada waktu saat ini. Jika Anda hanya ingin menggunakannya dalam satu lingkaran, sama sekali tidak perlu menyimpan hasil dalam daftar terlebih dahulu.
Daniel Brückner
20

Solusi sederhana:

static void Main()
{
    List<string> myList = new List<string> { "Jason", "Bob", "Frank", "Bob" };
    myList.RemoveAll(x => x == "Bob");

    foreach (string s in myList)
    {
        //
    }
}
CodeLikeBeaker
sumber
cara menghapus "Bob" dan "Jason" Maksudku beberapa daftar string?
Neo
19

Saya bertanya-tanya, apakah ada perbedaan antara RemoveAlldan Exceptdan pro menggunakan HashSet, jadi saya telah melakukan pemeriksaan kinerja cepat :)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace ListRemoveTest
{
    class Program
    {
        private static Random random = new Random( (int)DateTime.Now.Ticks );

        static void Main( string[] args )
        {
            Console.WriteLine( "Be patient, generating data..." );

            List<string> list = new List<string>();
            List<string> toRemove = new List<string>();
            for( int x=0; x < 1000000; x++ )
            {
                string randString = RandomString( random.Next( 100 ) );
                list.Add( randString );
                if( random.Next( 1000 ) == 0 )
                    toRemove.Insert( 0, randString );
            }

            List<string> l1 = new List<string>( list );
            List<string> l2 = new List<string>( list );
            List<string> l3 = new List<string>( list );
            List<string> l4 = new List<string>( list );

            Console.WriteLine( "Be patient, testing..." );

            Stopwatch sw1 = Stopwatch.StartNew();
            l1.RemoveAll( toRemove.Contains );
            sw1.Stop();

            Stopwatch sw2 = Stopwatch.StartNew();
            l2.RemoveAll( new HashSet<string>( toRemove ).Contains );
            sw2.Stop();

            Stopwatch sw3 = Stopwatch.StartNew();
            l3 = l3.Except( toRemove ).ToList();
            sw3.Stop();

            Stopwatch sw4 = Stopwatch.StartNew();
            l4 = l4.Except( new HashSet<string>( toRemove ) ).ToList();
            sw3.Stop();


            Console.WriteLine( "L1.Len = {0}, Time taken: {1}ms", l1.Count, sw1.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L2.Len = {0}, Time taken: {1}ms", l1.Count, sw2.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L3.Len = {0}, Time taken: {1}ms", l1.Count, sw3.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L4.Len = {0}, Time taken: {1}ms", l1.Count, sw3.Elapsed.TotalMilliseconds );

            Console.ReadKey();
        }


        private static string RandomString( int size )
        {
            StringBuilder builder = new StringBuilder();
            char ch;
            for( int i = 0; i < size; i++ )
            {
                ch = Convert.ToChar( Convert.ToInt32( Math.Floor( 26 * random.NextDouble() + 65 ) ) );
                builder.Append( ch );
            }

            return builder.ToString();
        }
    }
}

Hasil di bawah ini:

Be patient, generating data...
Be patient, testing...
L1.Len = 985263, Time taken: 13411.8648ms
L2.Len = 985263, Time taken: 76.4042ms
L3.Len = 985263, Time taken: 340.6933ms
L4.Len = 985263, Time taken: 340.6933ms

Seperti yang bisa kita lihat, opsi terbaik dalam hal ini adalah menggunakan RemoveAll(HashSet)

suszig
sumber
Kode ini: "l2.RemoveAll (HashSet baru <string> (toRemove) .Berisi);" seharusnya tidak mengkompilasi ... dan jika tes Anda benar maka mereka hanya kedua apa yang disarankan Jon Skeet.
Pascal
2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );mengkompilasi dengan baik hanya FYI
AzNjoE
9

Ini adalah pertanyaan yang sangat lama, tetapi saya menemukan cara yang sangat sederhana untuk melakukan ini:

authorsList = authorsList.Except(authors).ToList();

Perhatikan bahwa karena variabel kembali authorsListadalah a List<T>, yang IEnumerable<T>dikembalikan oleh Except()harus dikonversi ke a List<T>.

Carlos Martinez T
sumber
7

Anda dapat menghapus dengan dua cara

var output = from x in authorsList
             where x.firstname != "Bob"
             select x;

atau

var authors = from x in authorsList
              where x.firstname == "Bob"
              select x;

var output = from x in authorsList
             where !authors.Contains(x) 
             select x;

Saya memiliki masalah yang sama, jika Anda ingin output sederhana berdasarkan kondisi di mana Anda, maka solusi pertama lebih baik.

AsifQadri
sumber
Bagaimana saya memeriksa "Bob" atau "Billy"?
Si8
6

Katakan itu authorsToRemoveadalah IEnumerable<T>yang berisi elemen yang ingin Anda hapus authorsList.

Maka di sini ada cara lain yang sangat sederhana untuk menyelesaikan tugas penghapusan yang diminta oleh OP:

authorsList.RemoveAll(authorsToRemove.Contains);
atconway
sumber
5

Saya pikir Anda bisa melakukan sesuatu seperti ini

    authorsList = (from a in authorsList
                  where !authors.Contains(a)
                  select a).ToList();

Meskipun saya pikir solusi yang sudah diberikan menyelesaikan masalah dengan cara yang lebih mudah dibaca.

surut
sumber
4

Di bawah ini adalah contoh untuk menghapus elemen dari daftar.

 List<int> items = new List<int>() { 2, 2, 3, 4, 2, 7, 3,3,3};

 var result = items.Remove(2);//Remove the first ocurence of matched elements and returns boolean value
 var result1 = items.RemoveAll(lst => lst == 3);// Remove all the matched elements and returns count of removed element
 items.RemoveAt(3);//Removes the elements at the specified index
Sheo Dayal Singh
sumber
1

LINQ memiliki asal-usul dalam pemrograman fungsional, yang menekankan kekekalan objek, sehingga tidak menyediakan cara bawaan untuk memperbarui daftar asli di tempat.

Catatan tentang kekekalan (diambil dari jawaban SO lainnya):

Berikut ini definisi kekekalan dari Wikipedia .

Dalam pemrograman berorientasi objek dan fungsional, objek yang tidak dapat diubah adalah objek yang kondisinya tidak dapat dimodifikasi setelah dibuat.

Samuel Jack
sumber
0

saya pikir Anda hanya perlu menetapkan item dari daftar Penulis ke daftar baru untuk mengambil efek itu.

//assume oldAuthor is the old list
Author newAuthorList = (select x from oldAuthor where x.firstname!="Bob" select x).ToList();
oldAuthor = newAuthorList;
newAuthorList = null;
aj pergi
sumber
0

Agar kode tetap lancar (jika optimisasi kode tidak penting) dan Anda perlu melakukan beberapa operasi lebih lanjut dalam daftar:

authorsList = authorsList.Where(x => x.FirstName != "Bob").<do_some_further_Linq>;

atau

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Zbigniew Wiadro
sumber