Xóa các bản sao khỏi Danh sách <T> trong C #


487

Bất cứ ai cũng có một phương pháp nhanh chóng để sao chép lại một Danh sách chung trong C #?


4
Bạn có quan tâm đến thứ tự của các yếu tố trong kết quả? Điều này sẽ loại trừ một số giải pháp.
Đại tá Panic

Giải pháp một dòng:ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Harald Coppoolse 13/03/19

Câu trả lời:


227

Có lẽ bạn nên cân nhắc sử dụng Hashset .

Từ liên kết MSDN:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        HashSet<int> evenNumbers = new HashSet<int>();
        HashSet<int> oddNumbers = new HashSet<int>();

        for (int i = 0; i < 5; i++)
        {
            // Populate numbers with just even numbers.
            evenNumbers.Add(i * 2);

            // Populate oddNumbers with just odd numbers.
            oddNumbers.Add((i * 2) + 1);
        }

        Console.Write("evenNumbers contains {0} elements: ", evenNumbers.Count);
        DisplaySet(evenNumbers);

        Console.Write("oddNumbers contains {0} elements: ", oddNumbers.Count);
        DisplaySet(oddNumbers);

        // Create a new HashSet populated with even numbers.
        HashSet<int> numbers = new HashSet<int>(evenNumbers);
        Console.WriteLine("numbers UnionWith oddNumbers...");
        numbers.UnionWith(oddNumbers);

        Console.Write("numbers contains {0} elements: ", numbers.Count);
        DisplaySet(numbers);
    }

    private static void DisplaySet(HashSet<int> set)
    {
        Console.Write("{");
        foreach (int i in set)
        {
            Console.Write(" {0}", i);
        }
        Console.WriteLine(" }");
    }
}

/* This example produces output similar to the following:
 * evenNumbers contains 5 elements: { 0 2 4 6 8 }
 * oddNumbers contains 5 elements: { 1 3 5 7 9 }
 * numbers UnionWith oddNumbers...
 * numbers contains 10 elements: { 0 2 4 6 8 1 3 5 7 9 }
 */

11
nó nhanh đến mức không thể tin được ... 100.000 chuỗi với Danh sách cần 400 giây và 8 MB ram, giải pháp của riêng tôi mất 2,5 giây và 28 MB, hashset mất 0,1 giây !!! và ram 11MB
sasjaq

3
HashSet không có chỉ mục , do đó không phải lúc nào cũng có thể sử dụng nó. Tôi phải tạo một lần một danh sách lớn mà không trùng lặp và sau đó sử dụng nó ListViewtrong chế độ ảo. Rất nhanh để tạo một cái HashSet<>đầu tiên và sau đó chuyển đổi nó thành một List<>(vì vậy ListViewcó thể truy cập các mục theo chỉ mục). List<>.Contains()quá chậm
Sinatr

58
Sẽ có ích nếu có một ví dụ về cách sử dụng hàm băm trong ngữ cảnh cụ thể này.
Nathan McKaskle

23
Làm thế nào điều này có thể coi là một câu trả lời? Đó là một liên kết
mcont

2
Hashset là tuyệt vời trong hầu hết các trường hợp. Nhưng nếu bạn có một đối tượng như DateTime, nó sẽ so sánh theo tham chiếu chứ không phải theo giá trị, vì vậy bạn vẫn sẽ kết thúc với các bản sao.
Jason McKindly

813

Nếu bạn đang sử dụng .Net 3+, bạn có thể sử dụng Linq.

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();

14
Mã đó sẽ thất bại vì .Distinc () trả về IEnumerable <T>. Bạn phải thêm .ToList () vào nó.
ljs

Cách tiếp cận này chỉ có thể được sử dụng cho danh sách với các giá trị đơn giản.
Polaris

20
Không, nó hoạt động với các danh sách chứa các đối tượng thuộc bất kỳ loại nào. Nhưng bạn sẽ phải ghi đè bộ so sánh mặc định cho loại của bạn. Giống như vậy: công khai ghi đè bool Bằng (đối tượng obj) {...}
BaBu

1
Luôn luôn là một ý tưởng tốt để ghi đè ToString () và GetHashCode () với các lớp của bạn để loại điều này sẽ hoạt động.
B Bảy

2
Bạn cũng có thể sử dụng gói MoreLinQ Nuget có phương thức mở rộng .DistotypeBy (). Khá hữu ích.
yu_ominae

178

Làm thế nào về:

var noDupes = list.Distinct().ToList();

Trong .net 3.5?


Nó có trùng lặp danh sách không?
bóng tối

1
@darkgaze điều này chỉ tạo ra một danh sách khác chỉ với các mục duy nhất. Vì vậy, mọi bản sao sẽ bị xóa và bạn sẽ để lại một danh sách nơi mỗi vị trí có một đối tượng khác nhau.
hexagod

Điều này có hoạt động cho danh sách các danh sách các mục trong đó các mã mục trùng lặp và cần có danh sách duy nhất
venkat

90

Đơn giản chỉ cần khởi tạo Hashset với Danh sách cùng loại:

var noDupes = new HashSet<T>(withDupes);

Hoặc, nếu bạn muốn Danh sách được trả về:

var noDupsList = new HashSet<T>(withDupes).ToList();

3
... và nếu bạn cần List<T>sử dụng kết quảnew HashSet<T>(withDupes).ToList()
Tim Schmelter

47

Sắp xếp nó, sau đó kiểm tra hai và hai bên cạnh nhau, vì các bản sao sẽ co cụm lại với nhau.

Một cái gì đó như thế này:

list.Sort();
Int32 index = list.Count - 1;
while (index > 0)
{
    if (list[index] == list[index - 1])
    {
        if (index < list.Count - 1)
            (list[index], list[list.Count - 1]) = (list[list.Count - 1], list[index]);
        list.RemoveAt(list.Count - 1);
        index--;
    }
    else
        index--;
}

Ghi chú:

  • So sánh được thực hiện từ sau ra trước, để tránh phải đưa ra danh sách nghỉ dưỡng sau mỗi lần xóa
  • Ví dụ này hiện sử dụng Bộ giá trị C # để thực hiện hoán đổi, thay thế bằng mã thích hợp nếu bạn không thể sử dụng mã đó
  • Kết quả cuối cùng không còn được sắp xếp

1
Nếu tôi không nhầm, hầu hết các cách tiếp cận được đề cập ở trên chỉ là sự trừu tượng của chính thói quen này, phải không? Tôi đã có cách tiếp cận của bạn ở đây, Lasse, bởi vì đó là cách tôi hình dung về mặt tinh thần di chuyển qua dữ liệu. Nhưng, bây giờ tôi quan tâm đến sự khác biệt hiệu suất giữa một số đề xuất.
Ian Patrick Hughes

7
Thực hiện chúng và thời gian chúng, chỉ có cách để chắc chắn. Ngay cả ký hiệu Big-O sẽ không giúp bạn với các số liệu hiệu suất thực tế, chỉ có mối quan hệ hiệu quả tăng trưởng.
Lasse V. Karlsen

1
Tôi thích cách tiếp cận này, nó dễ mang theo các ngôn ngữ khác.
Jerry Liang

10
Đừng làm vậy. Nó siêu chậm. RemoveAtlà một hoạt động rất tốn kém trên mộtList
Clément

1
Clément là đúng. Một cách để cứu vãn điều này sẽ là bọc nó trong một phương thức mang lại một điều tra viên và chỉ trả về các giá trị riêng biệt. Ngoài ra, bạn có thể sao chép các giá trị vào một mảng hoặc danh sách mới.
JHubbard80

33

Tôi thích sử dụng lệnh này:

List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
                                                 .GroupBy(s => s.City)
                                                 .Select(grp => grp.FirstOrDefault())
                                                 .OrderBy(s => s.City)
                                                 .ToList();

Tôi có các trường này trong danh sách của mình: Id, StoreName, City, PostalCode Tôi muốn hiển thị danh sách các thành phố trong danh sách thả xuống có các giá trị trùng lặp. Giải pháp: Nhóm theo thành phố sau đó chọn cái đầu tiên cho danh sách.

Tôi hy vọng nó sẽ giúp :)


31

Nó làm việc cho tôi. chỉ cần sử dụng

List<Type> liIDs = liIDs.Distinct().ToList<Type>();

Thay thế "Loại" bằng loại mong muốn của bạn, ví dụ int.


1
Khác biệt là trong Linq, không phải System.Collections.Generic như báo cáo của trang MSDN.
Almo

5
Câu trả lời này (2012) dường như giống với hai câu trả lời khác trên trang này là từ năm 2008?
Jon Schneider

23

Như kronoz đã nói trong .Net 3.5, bạn có thể sử dụng Distinct().

Trong .Net 2 bạn có thể bắt chước nó:

public IEnumerable<T> DedupCollection<T> (IEnumerable<T> input) 
{
    var passedValues = new HashSet<T>();

    // Relatively simple dupe check alg used as example
    foreach(T item in input)
        if(passedValues.Add(item)) // True if item is new
            yield return item;
}

Điều này có thể được sử dụng để khấu trừ bất kỳ bộ sưu tập nào và sẽ trả về các giá trị theo thứ tự ban đầu.

Việc lọc một bộ sưu tập (như cả hai Distinct()và mẫu này đều nhanh hơn) sẽ nhanh hơn nhiều so với việc loại bỏ các mục khỏi nó.


Tuy nhiên, vấn đề với cách tiếp cận này là đó là O (N ^ 2), trái ngược với hashset. Nhưng ít nhất nó hiển nhiên những gì nó đang làm.
Tamas Czinege

1
@DrJokepu - thực sự tôi đã không nhận ra rằng các nhà HashSetxây dựng đã khấu trừ, điều này làm cho nó tốt hơn cho hầu hết các trường hợp. Tuy nhiên, điều này sẽ duy trì thứ tự sắp xếp, mà HashSetkhông.
Keith

1
HashSet <T> được giới thiệu vào 3,5
Thorn

1
@thorn thật sao? Rất khó để theo dõi. Trong trường hợp đó bạn chỉ có thể sử dụng một Dictionary<T, object>thay vào đó, thay thế .Containsvới .ContainsKey.Add(item)với.Add(item, null)
Keith

@Keith, theo thử nghiệm của tôi sẽ HashSetduy trì trật tự trong khi Distinct()không.
Dennis T --Reinstate Monica--

13

Một phương thức mở rộng có thể là một cách hay để đi ... một cái gì đó như thế này:

public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
    return listToDeduplicate.Distinct().ToList();
}

Và sau đó gọi như thế này, ví dụ:

List<int> myFilteredList = unfilteredList.Deduplicate();

11

Trong Java (tôi giả sử C # giống hoặc ít hơn):

list = new ArrayList<T>(new HashSet<T>(list))

Nếu bạn thực sự muốn thay đổi danh sách ban đầu:

List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);

Để duy trì trật tự, chỉ cần thay thế Hashset bằng LinkedHashset.


5
trong C # sẽ là: Danh sách <T> noDupes = Danh sách mới <T> (Hashset mới <T> (danh sách)); list.Clear (); list.AddRange (noDupes);
smohamed

Trong C #, cách này dễ dàng hơn: var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal

10

Điều này có sự khác biệt (các yếu tố không có các yếu tố trùng lặp) và chuyển đổi nó thành một danh sách một lần nữa:

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();

9

Sử dụng phương pháp Liên minh của Linq .

Lưu ý: Giải pháp này không đòi hỏi kiến ​​thức về Linq, ngoài việc nó tồn tại.

Bắt đầu bằng cách thêm phần sau vào đầu tệp lớp của bạn:

using System.Linq;

Bây giờ, bạn có thể sử dụng cách sau để xóa các bản sao khỏi một đối tượng được gọi là obj1:

obj1 = obj1.Union(obj1).ToList();

Lưu ý: Đổi tên obj1thành tên của đối tượng của bạn.

Làm thế nào nó hoạt động

  1. Lệnh Union liệt kê một trong mỗi mục của hai đối tượng nguồn. Vì obj1 là cả hai đối tượng nguồn, điều này làm giảm obj1 xuống một trong mỗi mục.

  2. Trả ToList()về một danh sách mới. Điều này là cần thiết, bởi vì các lệnh Linq như Uniontrả về kết quả dưới dạng kết quả IEnountable thay vì sửa đổi Danh sách gốc hoặc trả về Danh sách mới.


7

Là một phương thức trợ giúp (không có Linq):

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}

Tôi nghĩ rằng riêng biệt đã được thực hiện. Ngoài ra (nếu bạn đổi tên phương thức), nó sẽ hoạt động.
Andreas Reiff

6

Nếu bạn không quan tâm đến thứ tự bạn chỉ có thể xô các mục vào một HashSet, nếu bạn làm muốn duy trì trật tự bạn có thể làm một cái gì đó như thế này:

var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
    if (hs.Add(t))
        unique.Add(t);

Hoặc cách Linq:

var hs = new HashSet<T>();
list.All( x =>  hs.Add(x) );

Chỉnh sửa: Các HashSetphương pháp là O(N)thời gian và O(N)không gian trong khi phân loại và sau đó làm duy nhất (theo đề nghị của @ lassevk và những người khác) là O(N*lgN)thời gian và O(1)không gian vì vậy nó không quá rõ ràng với tôi (vì nó là ở cái nhìn đầu tiên) rằng cách sắp xếp là kém hơn (tôi xin lỗi vì đã bỏ phiếu tạm thời ...)


6

Đây là một phương pháp mở rộng để loại bỏ các bản sao liền kề tại chỗ. Gọi Sắp xếp () trước và vượt qua trong cùng một IComparer. Điều này sẽ hiệu quả hơn phiên bản của Lasse V. Karlsen, liên tục gọi RemoveAt (dẫn đến việc di chuyển nhiều bộ nhớ khối).

public static void RemoveAdjacentDuplicates<T>(this List<T> List, IComparer<T> Comparer)
{
    int NumUnique = 0;
    for (int i = 0; i < List.Count; i++)
        if ((i == 0) || (Comparer.Compare(List[NumUnique - 1], List[i]) != 0))
            List[NumUnique++] = List[i];
    List.RemoveRange(NumUnique, List.Count - NumUnique);
}

5

Cài đặt gói MoreLINEQ qua Nuget, bạn có thể dễ dàng phân biệt danh sách đối tượng theo một thuộc tính

IEnumerable<Catalogue> distinctCatalogues = catalogues.DistinctBy(c => c.CatalogueCode); 

3

Có thể dễ dàng hơn để đảm bảo rằng các bản sao không được thêm vào danh sách.

if(items.IndexOf(new_item) < 0) 
    items.add(new_item)

1
Tôi hiện đang làm như thế này nhưng bạn càng có nhiều mục thì việc kiểm tra trùng lặp càng lâu.
Robert Strauch

Tôi có cùng một vấn đề ở đây. Tôi đang sử dụng List<T>.Containsphương pháp này mỗi lần nhưng với hơn 1.000.000 mục. Quá trình này làm chậm ứng dụng của tôi. Tôi đang sử dụng một List<T>.Distinct().ToList<T>()thay thế đầu tiên.
RPDeshaies

Phương pháp này rất chậm
darkgaze

3

Bạn có thể sử dụng Liên minh

obj2 = obj1.Union(obj1).ToList();

7
Giải thích lý do tại sao nó sẽ hoạt động chắc chắn sẽ làm cho câu trả lời này tốt hơn
Igor B

2

Một cách khác trong .Net 2.0

    static void Main(string[] args)
    {
        List<string> alpha = new List<string>();

        for(char a = 'a'; a <= 'd'; a++)
        {
            alpha.Add(a.ToString());
            alpha.Add(a.ToString());
        }

        Console.WriteLine("Data :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t); });

        alpha.ForEach(delegate (string v)
                          {
                              if (alpha.FindAll(delegate(string t) { return t == v; }).Count > 1)
                                  alpha.Remove(v);
                          });

        Console.WriteLine("Unique Result :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t);});
        Console.ReadKey();
    }

2

Có nhiều cách để giải quyết - vấn đề trùng lặp trong Danh sách, dưới đây là một trong số đó:

List<Container> containerList = LoadContainer();//Assume it has duplicates
List<Container> filteredList = new  List<Container>();
foreach (var container in containerList)
{ 
  Container duplicateContainer = containerList.Find(delegate(Container checkContainer)
  { return (checkContainer.UniqueId == container.UniqueId); });
   //Assume 'UniqueId' is the property of the Container class on which u r making a search

    if(!containerList.Contains(duplicateContainer) //Add object when not found in the new class object
      {
        filteredList.Add(container);
       }
  }

Chúc mừng Ravi Ganesan


2

Đây là một giải pháp đơn giản không yêu cầu bất kỳ LINQ khó đọc nào hoặc bất kỳ sự sắp xếp nào trước đó của danh sách.

   private static void CheckForDuplicateItems(List<string> items)
    {
        if (items == null ||
            items.Count == 0)
            return;

        for (int outerIndex = 0; outerIndex < items.Count; outerIndex++)
        {
            for (int innerIndex = 0; innerIndex < items.Count; innerIndex++)
            {
                if (innerIndex == outerIndex) continue;
                if (items[outerIndex].Equals(items[innerIndex]))
                {
                    // Duplicate Found
                }
            }
        }
    }

Bạn có nhiều quyền kiểm soát hơn đối với các mục trùng lặp với phương pháp này. Thậm chí nhiều hơn nếu bạn có một cơ sở dữ liệu để cập nhật. Đối với chỉ số bên trong, tại sao không bắt đầu từ bên ngoài + 1 thay vì bắt đầu từ bắt đầu mọi lúc?
Nolmë Informatique

2

Câu trả lời của David J. là một phương pháp tốt, không cần thêm đối tượng, sắp xếp, v.v. Tuy nhiên, nó có thể được cải thiện:

for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)

Vì vậy, vòng lặp bên ngoài đi xuống dưới cùng cho toàn bộ danh sách, nhưng vòng lặp bên trong đi xuống dưới cùng "cho đến khi đạt được vị trí vòng lặp bên ngoài".

Vòng lặp bên ngoài đảm bảo toàn bộ danh sách được xử lý, vòng lặp bên trong tìm thấy các bản sao thực tế, những điều đó chỉ có thể xảy ra trong phần mà vòng lặp bên ngoài chưa được xử lý.

Hoặc nếu bạn không muốn thực hiện từ dưới lên cho vòng lặp bên trong, bạn có thể bắt đầu vòng lặp bên trong ở ngoài Index + 1.


2

Tất cả các câu trả lời sao chép danh sách, hoặc tạo một danh sách mới, hoặc sử dụng các chức năng chậm hoặc chỉ chậm một cách đau đớn.

Theo hiểu biết của tôi, đây là phương pháp nhanh nhất và rẻ nhất mà tôi biết (cũng được hỗ trợ bởi một lập trình viên rất có kinh nghiệm chuyên về tối ưu hóa vật lý thời gian thực).

// Duplicates will be noticed after a sort O(nLogn)
list.Sort();

// Store the current and last items. Current item declaration is not really needed, and probably optimized by the compiler, but in case it's not...
int lastItem = -1;
int currItem = -1;

int size = list.Count;

// Store the index pointing to the last item we want to keep in the list
int last = size - 1;

// Travel the items from last to first O(n)
for (int i = last; i >= 0; --i)
{
    currItem = list[i];

    // If this item was the same as the previous one, we don't want it
    if (currItem == lastItem)
    {
        // Overwrite last in current place. It is a swap but we don't need the last
       list[i] = list[last];

        // Reduce the last index, we don't want that one anymore
        last--;
    }

    // A new item, we store it and continue
    else
        lastItem = currItem;
}

// We now have an unsorted list with the duplicates at the end.

// Remove the last items just once
list.RemoveRange(last + 1, size - last - 1);

// Sort again O(n logn)
list.Sort();

Chi phí cuối cùng là:

nlogn + n + nlogn = n + 2nlogn = O (nlogn) khá đẹp.

Lưu ý về RemoveRange: Vì chúng tôi không thể thiết lập số lượng của danh sách và tránh sử dụng chức năng Xóa, tôi không biết chính xác tốc độ của thao tác này nhưng tôi đoán đó là cách nhanh nhất.


2

Nếu bạn có các lớp học kéo ProductCustomervà chúng tôi muốn xoá các mục trùng lặp khỏi danh sách của họ

public class Product
{
    public int Id { get; set; }
    public string ProductName { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public string CustomerName { get; set; }

}

Bạn phải định nghĩa một lớp chung trong mẫu dưới đây

public class ItemEqualityComparer<T> : IEqualityComparer<T> where T : class
{
    private readonly PropertyInfo _propertyInfo;

    public ItemEqualityComparer(string keyItem)
    {
        _propertyInfo = typeof(T).GetProperty(keyItem, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
    }

    public bool Equals(T x, T y)
    {
        var xValue = _propertyInfo?.GetValue(x, null);
        var yValue = _propertyInfo?.GetValue(y, null);
        return xValue != null && yValue != null && xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        var propertyValue = _propertyInfo.GetValue(obj, null);
        return propertyValue == null ? 0 : propertyValue.GetHashCode();
    }
}

sau đó, bạn có thể xóa các mục trùng lặp trong danh sách của bạn.

var products = new List<Product>
            {
                new Product{ProductName = "product 1" ,Id = 1,},
                new Product{ProductName = "product 2" ,Id = 2,},
                new Product{ProductName = "product 2" ,Id = 4,},
                new Product{ProductName = "product 2" ,Id = 4,},
            };
var productList = products.Distinct(new ItemEqualityComparer<Product>(nameof(Product.Id))).ToList();

var customers = new List<Customer>
            {
                new Customer{CustomerName = "Customer 1" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
            };
var customerList = customers.Distinct(new ItemEqualityComparer<Customer>(nameof(Customer.Id))).ToList();

mã này xóa các mục trùng lặp bằng cách Idnếu bạn muốn xóa các mục trùng lặp bởi thuộc tính khác, bạn có thể thay đổi nameof(YourClass.DuplicateProperty) tương tự nameof(Customer.CustomerName)sau đó xóa các mục trùng lặp theo CustomerNameThuộc tính.


1
  public static void RemoveDuplicates<T>(IList<T> list )
  {
     if (list == null)
     {
        return;
     }
     int i = 1;
     while(i<list.Count)
     {
        int j = 0;
        bool remove = false;
        while (j < i && !remove)
        {
           if (list[i].Equals(list[j]))
           {
              remove = true;
           }
           j++;
        }
        if (remove)
        {
           list.RemoveAt(i);
        }
        else
        {
           i++;
        }
     }  
  }

1

Một cách thực hiện trực quan đơn giản:

public static List<PointF> RemoveDuplicates(List<PointF> listPoints)
{
    List<PointF> result = new List<PointF>();

    for (int i = 0; i < listPoints.Count; i++)
    {
        if (!result.Contains(listPoints[i]))
            result.Add(listPoints[i]);
        }

        return result;
    }

Phương pháp này cũng chậm. Tạo một danh sách mới.
bóng tối
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.