Використання LINQ для видалення елементів зі списку <T>


654

Скажіть, що у мене є запит LINQ, такий як:

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

Враховуючи, що authorsListце тип List<Author>, як я можу видалити Authorелементи authorsList, які повертаються запитом у authors?

Або, кажучи іншим способом, як я можу видалити все, що дорівнює Боб, з прізвищем authorsList?

Примітка. Це спрощений приклад для цілей питання.

Відповіді:


1138

Що ж, було б простіше виключити їх в першу чергу:

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

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

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

Якщо вам дійсно потрібно зробити це на основі іншої колекції, я б використовував HashSet, RemoveAll і містить:

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

14
У чому причина використання HashSet для іншої колекції?
123 456 789 0

54
@LeoLuis: Це робить Containsперевірку швидкою і гарантує вам оцінити послідовність лише один раз.
Джон Скіт

2
@LeoLuis: Так, побудова HashSet з послідовності оцінює його лише один раз. Не впевнений, що ви маєте на увазі під "слабким набором колекцій".
Джон Скіт

2
@ AndréChristofferAndersen: Що ви розумієте під "застарілим"? Це все ще працює. Якщо у вас є List<T>, його добре використовувати.
Джон Скіт

4
@ AndréChristofferAndersen: Було б краще використовуватиauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Джон Скіт

133

Для цього краще використовувати List <T> .RemoveAll .

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

8
@Reed Copsey: Параметр лямбда у вашому прикладі міститься в дужках, тобто (x). Чи є для цього технічна причина? Чи вважається це гарною практикою?
Метт Девіс

24
Ні. Це потрібно з параметром> 1. З одним параметром це необов’язково, але це допомагає зберігати узгодженість.
Рід Копсей

48

Якщо вам дійсно потрібно видалити елементи, то що робити за винятком ()?
Ви можете видалити, грунтуючись на новому списку, або видалити на ходу, вклавши 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();

Except()це єдиний спосіб пройти посеред LINQ-оператора. IEnumerableне має Remove()ні RemoveAll().
Ярі

29

Ви не можете цього зробити зі стандартними операторами 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);
}

12
RemoveAll()не є оператором LINQ.
Даніель Брюкнер

Мої вибачення. Ви на 100% правильні. На жаль, я, здається, не можу змінити свою оцінку. Вибач за те.
Шай Коен

Removeтакож є методом List< T>, а не методом System.Linq.Emerable .
DavidRR

@Daniel, виправте мене, якщо я помиляюся, ми можемо уникнути .ToList () з «Де умова» для другого варіанту. Тобто код нижче буде працювати. var avtorList = GetAuthorList (); var автори = авториList.Where (a => a.FirstName == "Боб"); foreach (var автор в авторах) {authorList.Remove (автор); }
Сай

Так, це спрацює. Перетворення його в список потрібно лише в тому випадку, якщо вам потрібен список, щоб передати його якомусь методу або якщо ви хочете пізніше додати або видалити більше матеріалів. Це також може бути корисним, якщо вам доведеться перераховувати послідовність кілька разів, оскільки тоді вам доведеться лише оцінити потенційно дорогий, коли умова один раз, або якщо результат може змінитися між двома перерахуваннями, наприклад, тому що умова залежить від поточного часу. Якщо ви хочете використовувати його лише в одному циклі, абсолютно не потрібно спочатку зберігати результат у списку.
Даніель Брюкнер

20

Просте рішення:

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

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

як видалити "Bob" та "Jason" я маю на увазі кратні в списку рядків?
Нео

19

Мені було цікаво, чи є якась різниця між 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)


Цей код: "l2.RemoveAll (новий HashSet <string> (toRemove) .Contains);" не слід збирати ... і якщо ваші тести є правильними, вони лише секунду того, що вже запропонував Джон Скіт.
Паскаль

2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );складає чудово просто FYI
AzNjoE

9

Це дуже старе питання, але я знайшов дійсно простий спосіб зробити це:

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

Зауважте, що оскільки змінна return authorsListє a List<T>, що IEnumerable<T>повертається через Except()повинен бути перетворений в a List<T>.


7

Видалити його можна двома способами

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;

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


Як я можу перевірити "Боб" або "Біллі"?
Si8

6

Скажіть, що authorsToRemoveце IEnumerable<T>вміст елементів, з яких потрібно видалити authorsList.

Тоді ось ще один дуже простий спосіб виконати завдання з видалення, яке задає ОП:

authorsList.RemoveAll(authorsToRemove.Contains);

5

Я думаю, ви могли б зробити щось подібне

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

Хоча я думаю, що вже дані рішення вирішують проблему більш читабельним чином.


4

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

 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 бере свій початок у функціональному програмуванні, що підкреслює незмінність об'єктів, тому він не забезпечує вбудований спосіб оновлення оригінального списку на місці.

Примітка про незмінність (взята з іншої відповіді ТА):

Ось визначення незмінності з Вікіпедії .

У об'єктно-орієнтованому та функціональному програмуванні незмінним об'єктом є об'єкт, стан якого не може бути змінений після його створення.


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

Щоб зберегти код вільним (якщо оптимізація коду не є вирішальним), вам потрібно буде виконати деякі подальші операції зі списку:

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

або

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.