Usando LINQ para eliminar elementos de una Lista <T>

655

Digamos que tengo una consulta LINQ como:

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

Dado que authorsListes de tipo List<Author>, ¿cómo puedo eliminar los Authorelementos authorsListque devuelve la consulta authors?

O, dicho de otro modo, ¿cómo puedo eliminar todos los nombres que igualan a Bob authorsList?

Nota: Este es un ejemplo simplificado para los propósitos de la pregunta.

TK
fuente

Respuestas:

1139

Bueno, sería más fácil excluirlos en primer lugar:

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

Sin embargo, eso solo cambiaría el valor de en authorsListlugar de eliminar a los autores de la colección anterior. Alternativamente, puede usar RemoveAll:

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

Si realmente necesita hacerlo en base a otra colección, usaría un HashSet, RemoveAll y Contiene:

var setToRemove = new HashSet<Author>(authors);
authorsList.RemoveAll(x => setToRemove.Contains(x));
Jon Skeet
fuente
14
¿Cuál es la razón para usar HashSet para otra colección?
123456789 0
54
@LeoLuis: hace que la Containsverificación sea rápida y garantiza que solo evalúe la secuencia una vez.
Jon Skeet
2
@LeoLuis: Sí, construir un HashSet a partir de una secuencia solo lo evalúa una vez. No estoy seguro de lo que quiere decir con "conjunto de colección débil".
Jon Skeet
2
@ AndréChristofferAndersen: ¿Qué quiere decir con "anticuado"? Aún funciona. Si tienes un List<T>, está bien usarlo.
Jon Skeet
44
@ AndréChristofferAndersen: Sería mejor usarloauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Jon Skeet
133

Sería mejor usar List <T> .RemoveAll para lograr esto.

authorsList.RemoveAll((x) => x.firstname == "Bob");
Reed Copsey
fuente
8
@Reed Copsey: el parámetro lambda en su ejemplo está encerrado entre paréntesis, es decir, (x). ¿Hay alguna razón técnica para esto? ¿Se considera una buena práctica?
Matt Davis, el
24
No. Se requiere con> 1 parámetro. Con un solo parámetro, es opcional, pero ayuda a mantener la coherencia.
Reed Copsey
48

Si realmente necesita eliminar elementos, ¿qué pasa con Except ()?
Puede eliminar en función de una nueva lista, o eliminar sobre la marcha anidando el 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
fuente
Except()es la única forma de ir en medio de la declaración LINQ. IEnumerableno tiene Remove()ni RemoveAll().
Jari Turkia
29

No puede hacer esto con operadores estándar de LINQ porque LINQ proporciona soporte de consulta, no actualización.

Pero puede generar una nueva lista y reemplazar la anterior.

var authorsList = GetAuthorList();

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

O puede eliminar todos los elementos authorsen una segunda pasada.

var authorsList = GetAuthorList();

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

foreach (var author in authors)
{
    authorList.Remove(author);
}
Daniel Brückner
fuente
12
RemoveAll()No es un operador LINQ.
Daniel Brückner
Mis disculpas. Estás 100% correcto. Desafortunadamente, parece que no puedo revertir mi voto negativo. Lo siento por eso.
Shai Cohen
Removetambién es un método List< T>, no un método System.Linq.Enumerable .
DavidRR
@Daniel, corrígeme si me equivoco, podemos evitar .ToList () desde donde la condición para la segunda opción. Es decir, debajo del código funcionará. var authorList = GetAuthorList (); var author = authorList.Where (a => a.Primer nombre == "Bob"); foreach (autor var en autores) {authorList.Remove (autor); }
Sai
Sí, esto funcionará. Convertirlo en una lista solo es necesario si necesita una lista para pasarla a algún método o si desea agregar o eliminar más cosas más tarde. También puede ser útil si tiene que enumerar la secuencia varias veces porque solo tiene que evaluar una vez la condición potencialmente costosa o si el resultado puede cambiar entre dos enumeraciones, por ejemplo, porque la condición depende de la hora actual. Si solo desea usarlo en un ciclo, no hay absolutamente ninguna necesidad de almacenar primero el resultado en una lista.
Daniel Brückner
20

Solución simple:

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

    foreach (string s in myList)
    {
        //
    }
}
CodeLikeBeaker
fuente
¿Cómo eliminar "Bob" y "Jason" me refiero a múltiples en la lista de cadenas?
Neo
19

Me preguntaba si hay alguna diferencia entre RemoveAlly Exceptlas ventajas del uso HashSet, por lo que he realizado una comprobación rápida del rendimiento :)

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

Resultados a continuación:

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

Como podemos ver, la mejor opción en ese caso es usar RemoveAll(HashSet)

suszig
fuente
Este código: "l2.RemoveAll (nuevo HashSet <string> (toRemove) .Contains);" no debe compilarse ... y si sus pruebas son correctas, entonces solo secundan lo que Jon Skeet ya sugirió.
Pascal
2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );compila bien solo para su información
AzNjoE
9

Esta es una pregunta muy antigua, pero encontré una forma muy simple de hacer esto:

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

Tenga en cuenta que dado que la variable de retorno authorsListes a List<T>, el IEnumerable<T>devuelto por Except()debe convertirse en a List<T>.

Carlos Martinez T
fuente
7

Puedes eliminar de dos maneras

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

o

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

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

Tuve el mismo problema, si desea una salida simple basada en su condición where, entonces la primera solución es mejor.

AsifQadri
fuente
¿Cómo puedo verificar "Bob" o "Billy"?
Si8
6

Supongamos que authorsToRemovees un IEnumerable<T>elemento que contiene los elementos que desea eliminar authorsList.

Luego, aquí hay otra forma muy simple de realizar la tarea de eliminación solicitada por el OP:

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

Creo que podrías hacer algo como esto

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

Aunque creo que las soluciones ya dadas resuelven el problema de una manera más legible.

ebrown
fuente
4

A continuación se muestra el ejemplo para eliminar el elemento de la lista.

 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
fuente
1

LINQ tiene sus orígenes en la programación funcional, que enfatiza la inmutabilidad de los objetos, por lo que no proporciona una forma integrada de actualizar la lista original en el lugar.

Nota sobre la inmutabilidad (tomada de otra respuesta SO):

Aquí está la definición de inmutabilidad de Wikipedia .

En la programación orientada a objetos y funcional, un objeto inmutable es un objeto cuyo estado no puede modificarse después de su creación.

Samuel Jack
fuente
0

Creo que solo tiene que asignar los elementos de la lista de Autor a una nueva lista para tener ese efecto.

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

Para mantener el código fluido (si la optimización del código no es crucial) y necesitaría realizar algunas operaciones adicionales en la lista:

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

o

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