Sử dụng LINQ để xóa các thành phần khỏi Danh sách <T>


654

Nói rằng tôi có truy vấn LINQ như:

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

Cho rằng đó authorsListlà loại List<Author>, làm thế nào tôi có thể xóa các Authoryếu tố từ authorsListđó được truy vấn trả về authors?

Hoặc, đặt một cách khác, làm thế nào tôi có thể xóa tất cả Bob của tên đầu tiên khỏi authorsList?

Lưu ý: Đây là một ví dụ đơn giản cho các mục đích của câu hỏi.

Câu trả lời:


1138

Chà, sẽ dễ dàng hơn để loại trừ chúng ở nơi đầu tiên:

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

Tuy nhiên, điều đó sẽ chỉ thay đổi giá trị authorsListthay vì loại bỏ các tác giả khỏi bộ sưu tập trước đó. Ngoài ra, bạn có thể sử dụng RemoveAll:

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

Nếu bạn thực sự cần phải làm điều đó dựa trên bộ sưu tập khác, tôi sẽ sử dụng Hashset, Remove ALL và Chứa:

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

14
Lý do sử dụng Hashset cho bộ sưu tập khác là gì?
123 456 789 0

54
@LeoLuis: Nó giúp Containskiểm tra nhanh và đảm bảo bạn chỉ đánh giá trình tự một lần.
Jon Skeet

2
@LeoLuis: Có, xây dựng Hashset từ một chuỗi chỉ đánh giá nó một lần. Không chắc chắn những gì bạn có nghĩa là "bộ sưu tập yếu".
Jon Skeet

2
@ AndréChristofferAndersen: Ý của bạn là "lỗi thời" là gì? Nó vẫn làm việc. Nếu bạn đã có một List<T>, nó là tốt để sử dụng nó.
Jon Skeet

4
@ AndréChristofferAndersen: Sẽ tốt hơn khi sử dụngauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Jon Skeet

133

Sẽ tốt hơn nếu sử dụng Danh sách <T> .Remove ALL để thực hiện việc này.

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

8
@Reed Copsey: Tham số lambda trong ví dụ của bạn được đặt trong ngoặc đơn, tức là (x). Có một lý do kỹ thuật cho việc này? Được coi là thực hành tốt?
Matt Davis

24
Không. Nó được yêu cầu với> 1 tham số. Với một tham số duy nhất, đó là tùy chọn, nhưng nó giúp giữ tính nhất quán.
Sậy Copsey

48

Nếu bạn thực sự cần phải xóa các mục thì còn gì ngoại trừ ()?
Bạn có thể xóa dựa trên danh sách mới hoặc xóa khi đang di chuyển bằng cách lồng 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()là cách duy nhất để đi giữa tuyên bố LINQ. IEnumerablekhông có Remove()cũng không có RemoveAll().
Jari Turkia

29

Bạn không thể làm điều này với các toán tử LINQ tiêu chuẩn vì LINQ cung cấp truy vấn, không cập nhật hỗ trợ.

Nhưng bạn có thể tạo một danh sách mới và thay thế danh sách cũ.

var authorsList = GetAuthorList();

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

Hoặc bạn có thể loại bỏ tất cả các mục authorstrong một lần thứ hai.

var authorsList = GetAuthorList();

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

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

12
RemoveAll()không phải là toán tử LINQ.
Daniel Brückner

Lời xin lỗi của tôi. Bạn đúng 100%. Thật không may, tôi dường như không thể đảo ngược downvote của tôi. Xin lỗi vì điều đó.
Shai Cohen

Removecũng là một phương thức List< T>, không phải là phương thức System.Linq.Enumerable .
DavidRR

@Daniel, Sửa lỗi cho tôi nếu tôi sai, chúng tôi có thể tránh .ToList () từ vị trí dẫn cho tùy chọn thứ hai. Tức là mã dưới đây sẽ làm việc. var AuthorList = GetAuthorList (); var tác giả = tác giảList.Where (a => a.FirstName == "Bob"); foreach (var tác giả trong tác giả) {AuthorList.Remove (tác giả); }
Sai

Vâng, điều này sẽ làm việc. Biến nó thành một danh sách chỉ được yêu cầu nếu bạn cần một danh sách để chuyển nó sang một phương thức nào đó hoặc nếu bạn muốn thêm hoặc xóa nhiều thứ sau này. Nó cũng có thể hữu ích nếu bạn phải liệt kê chuỗi nhiều lần vì sau đó bạn chỉ phải đánh giá điều kiện có thể tốn kém một lần hoặc nếu kết quả có thể thay đổi giữa hai lần liệt kê, vì điều kiện phụ thuộc vào thời gian hiện tại. Nếu bạn chỉ muốn sử dụng nó trong một vòng lặp, hoàn toàn không cần lưu trữ kết quả trước tiên trong danh sách.
Daniel Brückner

20

Giải pháp đơn giản:

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

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

Làm thế nào để loại bỏ "Bob" và "Jason" Ý tôi là nhiều trong danh sách chuỗi?
Neo

19

Tôi đã tự hỏi, nếu có bất kỳ sự khác biệt giữa RemoveAllExceptưu điểm của việc sử dụng HashSet, vì vậy tôi đã thực hiện kiểm tra hiệu suất nhanh chóng :)

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

Kết quả dưới đây:

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

Như chúng ta có thể thấy, tùy chọn tốt nhất trong trường hợp đó là sử dụng RemoveAll(HashSet)


Mã này: "l2.Remove ALL (new Hashset <string> (toRemove) .Contains);" không nên biên dịch ... và nếu các bài kiểm tra của bạn là chính xác thì chúng chỉ là thứ hai những gì Jon Skeet đã đề xuất.
Pascal

2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );biên dịch tốt chỉ FYI
AzNjoE

9

Đây là một câu hỏi rất cũ, nhưng tôi đã tìm thấy một cách thực sự đơn giản để làm điều này:

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

Lưu ý rằng vì biến trả về authorsListlà a List<T>, nên IEnumerable<T>trả về Except()phải được chuyển đổi thành a List<T>.


7

Bạn có thể loại bỏ theo hai cách

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

hoặc là

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

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

Tôi đã có một vấn đề tương tự, nếu bạn muốn đầu ra đơn giản dựa trên điều kiện của bạn, thì giải pháp đầu tiên là tốt hơn.


Làm cách nào tôi có thể kiểm tra "Bob" hoặc "Billy"?
Si8

6

Nói rằng đó authorsToRemovelà một trong IEnumerable<T>đó có chứa các yếu tố bạn muốn loại bỏ authorsList.

Sau đó, đây là một cách rất đơn giản khác để hoàn thành nhiệm vụ loại bỏ mà OP yêu cầu:

authorsList.RemoveAll(authorsToRemove.Contains);

5

Tôi nghĩ bạn có thể làm một cái gì đó như thế này

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

Mặc dù tôi nghĩ rằng các giải pháp đã được đưa ra giải quyết vấn đề theo cách dễ đọc hơn.


4

Dưới đây là ví dụ để loại bỏ phần tử khỏi danh sách.

 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 có nguồn gốc từ lập trình chức năng, trong đó nhấn mạnh đến tính bất biến của các đối tượng, vì vậy nó không cung cấp một cách tích hợp để cập nhật danh sách ban đầu tại chỗ.

Lưu ý về tính bất biến (lấy từ câu trả lời SO khác):

Dưới đây là định nghĩa về tính bất biến từ Wikipedia .

Trong lập trình hướng đối tượng và chức năng, một đối tượng bất biến là một đối tượng có trạng thái không thể sửa đổi sau khi được tạo.


0

Tôi nghĩ rằng bạn chỉ cần gán các mục từ danh sách Tác giả vào một danh sách mới để có hiệu lực đó.

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

0

Để giữ cho mã thông thạo (nếu tối ưu hóa mã không quan trọng) và bạn sẽ cần thực hiện thêm một số thao tác trong danh sách:

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

hoặc là

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.