Listedeki öğeleri kaldırmak için LINQ kullanma <T>


655

Diyelim ki LINQ sorgusu gibi:

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

Bu tür authorsListolduğu göz önüne alındığında List<Author>,Author elemanları authorsListiçine sorgu tarafından döndürülen authors?

Ya da başka bir yolla, tüm adın eşit olan Bob'unu nasıl silebilirim authorsList ?

Not: Bu, sorunun amaçları için basitleştirilmiş bir örnektir.

Yanıtlar:


1139

İlk etapta onları hariç tutmak daha kolay olurdu:

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

Ancak bu sadece authorsListyazarları önceki koleksiyondan kaldırmak yerine değerini değiştirecektir . Alternatif olarak şunları kullanabilirsiniz RemoveAll:

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

Gerçekten başka bir koleksiyona göre yapmanız gerekiyorsa, bir HashSet, RemoveAll ve İçerir:

var setToRemove = new HashSet<Author>(authors);
authorsList.RemoveAll(x => setToRemove.Contains(x));

14
Başka bir koleksiyon için HashSet kullanmanın nedeni nedir?
123456789 0

54
@LeoLuis: ContainsKontrolü hızlandırır ve diziyi yalnızca bir kez değerlendirmenizi sağlar.
Jon Skeet

2
@LeoLuis: Evet, bir sekanstan bir HashSet oluşturmak onu yalnızca bir kez değerlendirir. "Zayıf toplama seti" ile ne demek istediğinizden emin değilim.
Jon Skeet

2
@ AndréChristofferAndersen: "Eski" ile ne demek istiyorsun? Hala çalışıyor. Eğer varsa List<T>, kullanmakta fayda var.
Jon Skeet

4
@ AndréChristofferAndersen: Kullanmak daha iyi olurduauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Jon Skeet

133

Bunu yapmak için <T> .RemoveAll Listesini kullanmak daha iyi olur .

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

8
@Reed Copsey: Örneğinizdeki lambda parametresi parantez içine alınmıştır, yani (x). Bunun teknik bir nedeni var mı? İyi uygulama olarak mı görülüyor?
Matt Davis

24
Hayır.> 1 parametresi ile gereklidir. Tek bir parametre ile isteğe bağlıdır, ancak tutarlılığın korunmasına yardımcı olur.
Reed Copsey

48

Gerçekten öğeleri kaldırmanız gerekiyorsa Peki () hariç ne olacak?
Yeni bir listeye dayalı olarak kaldırabilir veya Linq'i iç içe yerleştirerek anında kaldırabilirsiniz.

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

Except()LINQ ifadesinin ortasına gitmenin tek yoludur. IEnumerableyok Remove()ne de RemoveAll().
Jari Turkia

29

LINQ güncelleme desteği değil, sorgu sağladığı için bunu standart LINQ operatörleriyle yapamazsınız.

Ancak yeni bir liste oluşturabilir ve eskisini değiştirebilirsiniz.

var authorsList = GetAuthorList();

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

Veya authorsikinci bir geçişteki tüm öğeleri kaldırabilirsiniz .

var authorsList = GetAuthorList();

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

foreach (var author in authors)
{
    authorList.Remove(author);
}

12
RemoveAll()bir LINQ operatörü değildir.
Daniel Brückner

Özür dilerim. % 100 haklısın. Ne yazık ki, düşüşümü tersine çeviremiyorum. Bunun için üzgünüm.
Shai Cohen

Removeaynı zamanda bir System.Linq.Enumerable yöntemi değil, bir List< T> yöntemidir.
DavidRR

@Daniel, Yanlışsam beni düzeltebiliriz .ToList () Nereden ikinci seçenek için koşul. Aşağıdaki kod çalışacaktır. var authorsList = GetAuthorList (); var authors = authorsList.Where (a => a.FirstName == "Bob"); foreach (yazarlarda var yazar) {authorList.Remove (yaratıcı); }
Sai

Evet, işe yarayacak. Bir listeye dönüştürmek, yalnızca bir yönteme geçmek için bir listeye ihtiyacınız varsa veya daha sonra başka şeyler eklemek veya kaldırmak istiyorsanız gereklidir. Sekansı birden çok kez numaralandırmanız gerektiğinde de yararlı olabilir, çünkü o zaman sadece pahalı olan durumun durumunu bir kez veya iki numaralandırma arasında değişebiliyorsa, örneğin durum geçerli saate bağlı olduğundan değerlendirmeniz gerekir. Sadece bir döngüde kullanmak istiyorsanız, sonucu ilk önce bir listede saklamanıza kesinlikle gerek yoktur.
Daniel Brückner

20

Basit çözüm:

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

    foreach (string s in myList)
    {
        //
    }
}

"Bob" ve "Jason" nasıl kaldırılır Dize listesinde çoklu demek?
Neo

19

Arasında herhangi bir fark olup olmadığını merak ediyordum RemoveAllve Exceptve kullanmanın artıları HashSetBen hızlı bir performans denetimini yapmış, böylece :)

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

Aşağıdaki sonuçlar:

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

Gördüğümüz gibi, bu durumda en iyi seçenek RemoveAll(HashSet)


Bu kod: "l2.RemoveAll (yeni HashSet <string> (toRemove) .Contains);" derlememelisiniz ... ve testleriniz doğruysa Jon Skeet'in önerdiği şeyden sadece ikincisidir.
Pascal

2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );sadece FYI derler
AzNjoE

9

Bu çok eski bir soru, ama bunu yapmanın gerçekten basit bir yolunu buldum:

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

Dönüş değişkeni authorsLista List<T>olduğundan, IEnumerable<T>döndürülen Except()öğenin a değerine dönüştürülmesi gerektiğini unutmayın List<T>.


7

İki şekilde kaldırabilirsiniz

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

veya

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

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

Nerede bulunduğunuz duruma göre basit çıktı istiyorsanız, o zaman ilk çözüm daha iyi.


"Bob" veya "Billy" yi nasıl kontrol edebilirim?
Si8

6

Bunun authorsToRemove, IEnumerable<T>kaldırmak istediğiniz öğeleri içeren bir olduğunu varsayalım authorsList.

O zaman burada OP tarafından sorulan kaldırma görevini gerçekleştirmenin çok basit bir yolu var:

authorsList.RemoveAll(authorsToRemove.Contains);

5

Bence böyle bir şey yapabilirsin

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

Zaten verilen çözümlerin sorunu daha okunabilir bir şekilde çözdüğünü düşünüyorum.


4

Öğeyi listeden kaldırma örneği aşağıdadır.

 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

1

LINQ, nesnelerin değişmezliğini vurgulayan fonksiyonel programlamada kökenlidir, bu nedenle orijinal listeyi yerinde güncellemek için yerleşik bir yol sağlamaz.

Değişmezlik hakkında not (başka bir SO cevabından alınmıştır):

Vikipedi den immutability'nin tanımı .

Nesne yönelimli ve fonksiyonel programlamada, değişmez bir nesne, durumu oluşturulduktan sonra değiştirilemeyen bir nesnedir.


0

Sanırım sadece bu etkiyi almak için Yazar listesinden yeni bir listeye öğe atamanız gerekiyor.

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

0

Kodu akıcı tutmak için (kod optimizasyonu çok önemli değilse) ve listede başka işlemler yapmanız gerekir:

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

veya

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.