Использование LINQ для удаления элементов из списка <T>

514

Скажите, что у меня есть запрос LINQ, например:

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

Учитывая, что authorsList имеет тип List<Author>, как я могу удалить элементы Author из authorsList, которые возвращаются запросом в authors?

Или, по-другому, как я могу удалить все первое имя, равное Бобу из authorsList?

Примечание. Это упрощенный пример для целей вопроса.

Теги:
linq
list

15 ответов

981
Лучший ответ

Ну, было бы проще исключить их в первую очередь:

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

Однако это просто изменит значение authorsList вместо удаления авторов из предыдущей коллекции. Кроме того, вы можете использовать RemoveAll:

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

Если вам действительно нужно сделать это на основе другой коллекции, я бы использовал HashSet, RemoveAll и Contains:

var setToRemove = new HashSet<Author>(authors);
authorsList.RemoveAll(x => setToRemove.Contains(x));
  • 11
    В чем причина использования HashSet для другой коллекции?
  • 48
    @LeoLuis: Это делает проверку Contains быстрой и гарантирует, что вы оцените последовательность только один раз.
Показать ещё 20 комментариев
117

Лучше использовать List <T> .RemoveAll, чтобы выполнить это.

authorsList.RemoveAll((x) => x.firstname == "Bob");
  • 6
    @Reed Copsey: лямбда-параметр в вашем примере заключен в скобки, т. Е. (X). Есть ли техническая причина для этого? Это считается хорошей практикой?
  • 18
    Требуется с> 1 параметром. С одним параметром это необязательно, но это помогает поддерживать согласованность.
36

Если вам действительно нужно удалить элементы, то что насчет Except()?
Вы можете удалить его на основе нового списка или удалить "на лету", вложив 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();
19

Вы не можете сделать это со стандартными операторами LINQ, поскольку LINQ предоставляет запрос, а не поддержку обновления.

Но вы можете создать новый список и заменить старый.

var authorsList = GetAuthorList();

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

Или вы можете удалить все элементы в authors за второй проход.

var authorsList = GetAuthorList();

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

foreach (var author in authors)
{
    authorList.Remove(author);
}
  • 0
    Неправильно. RemoveAll() делает обновление списка на месте.
  • 9
    RemoveAll() не является оператором LINQ.
Показать ещё 5 комментариев
18

Простое решение:

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

    foreach (string s in myList)
    {
        //
    }
}
  • 0
    как удалить "Боб" и "Джейсон" Я имею в виду несколько в списке строк?
13

Я блуждал, если есть какая-то разница между RemoveAll и Except и преимуществами использования HashSet, поэтому я сделал быструю проверку производительности:)

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

Результаты ниже:

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

Как мы видим, лучшим вариантом в этом случае является использование RemoveAll (HashSet)

  • 0
    Этот код: "l2.RemoveAll (новый HashSet <string> (toRemove) .Contains);" не должны компилироваться ... и если ваши тесты верны, то они просто вторые, что уже предложил Джон Скит.
  • 1
    l2.RemoveAll( new HashSet<string>( toRemove ).Contains ); компилирует нормально только к вашему сведению
7

Это очень старый вопрос, но я нашел действительно простой способ сделать это:

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

Обратите внимание, что поскольку возвращаемая переменная authorsList является List<T>, IEnumerable<T>, возвращаемая Except(), должна быть преобразована в List<T>.

6

Вы можете удалить двумя способами.

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

или

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

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

У меня была такая же проблема, если вы хотите простой вывод на основе вашего условия, тогда первое решение лучше.

  • 0
    Как я могу проверить «Боб» или «Билли»?
5

Скажите, что authorsToRemove является IEnumerable<T>, который содержит элементы, которые вы хотите удалить из authorsList.

Тогда вот еще один очень простой способ выполнить задачу удаления, заданную OP:

authorsList.RemoveAll(authorsToRemove.Contains);
4

Я думаю, вы могли бы сделать что-то вроде этого

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

Хотя я думаю, что решения, которые уже были решены, решают проблему более читаемым способом.

3

Ниже приведен пример удаления элемента из списка.

 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
0

Чтобы код оставался свободным (если оптимизация кода не имеет решающего значения), и вам нужно будет выполнить некоторые дополнительные операции в списке:

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

или

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
0

Очень просто:

authorsList.RemoveAll((x) => x.firstname == "Bob");
  • 12
    Почему вы написали этот ответ через 6 лет после принятого ответа, который уже содержит то, что вы говорите?
0

Я думаю, вам просто нужно назначить элементы из списка авторов в новый список, чтобы принять этот эффект.

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

LINQ берет свое начало в функциональном программировании, которое подчеркивает неизменность объектов, поэтому оно не обеспечивает встроенный способ обновления исходного списка на месте.

Примечание по неизменяемости (взято из другого ответа SO):

Вот определение неизменяемости из Википедии (ссылка)

"В объектно-ориентированном и функциональном программировании неизменяемым объектом является объект, состояние которого не может быть изменено после его создания".

Ещё вопросы

Сообщество Overcoder
Наверх
Меню