从 C# 中的 List<T> 中删除重复项

Posted

技术标签:

【中文标题】从 C# 中的 List<T> 中删除重复项【英文标题】:Remove duplicates from a List<T> in C# 【发布时间】:2010-09-08 01:05:43 【问题描述】:

任何人有在 C# 中对通用列表进行重复数据删除的快速方法吗?

【问题讨论】:

你关心结果中元素的顺序吗?这将排除一些解决方案。 一条线解决方案:ICollection&lt;MyClass&gt; withoutDuplicates = new HashSet&lt;MyClass&gt;(inputList); 【参考方案1】:

排序,然后检查两个和两个并排,因为重复项会聚集在一起。

类似这样的:

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--;

注意事项:

比较是从后到前进行的,以避免每次删除后都必须使用度假村列表 此示例现在使用 C# 值元组进行交换,如果您不能使用,请使用适当的代码替换 最终结果不再排序

【讨论】:

如果我没记错的话,上面提到的大多数方法只是这些例程的抽象,对吧? Lasse,我会在这里采用你的方法,因为这是我在脑海中描绘数据移动的方式。但是,现在我对一些建议之间的性能差异感兴趣。 实施它们并为它们计时,这是确定的唯一方法。即使是 Big-O 符号也无法帮助您了解实际的性能指标,而只是一种增长效应关系。 我喜欢这种方法,它更易于移植到其他语言。 不要那样做。它超级慢。 RemoveAtList 上是一项非常昂贵的操作 克莱门特是对的。解决这个问题的一种方法是将其包装在一个使用枚举器产生并且只返回不同值的方法中。或者,您可以将值复制到新的数组或列表中。【参考方案2】:

也许您应该考虑使用HashSet。

来自 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 
 */

【讨论】:

它的速度令人难以置信... 100.000 个字符串 List 需要 400 秒和 8MB 内存,我自己的解决方案需要 2.5 秒和 28MB,hashset 需要 0.1 秒!!!和 11MB 内存 HashSet doesn't have an index ,因此并不总是可以使用它。我必须创建一次没有重复的巨大列表,然后在虚拟模式下将其用于ListView。首先创建 HashSet&lt;&gt; 然后将其转换为 List&lt;&gt; 非常快(因此 ListView 可以通过索引访问项目)。 List&lt;&gt;.Contains() 太慢了。 如果有一个如何在这个特定上下文中使用哈希集的示例,将会有所帮助。 这怎么能算是一个答案?这是一个链接 HashSet 在大多数情况下都很棒。但是如果你有一个像 DateTime 这样的对象,它会按引用而不是按值进行比较,所以你仍然会得到重复。【参考方案3】:

在 Java 中(我假设 C# 或多或少相同):

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

如果你真的想改变原始列表:

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

要保持顺序,只需将 HashSet 替换为 LinkedHashSet。

【讨论】:

在 C# 中是: List noDupes = new List(new HashSet(list));列表.清除(); list.AddRange(noDupes); 在 C# 中,这样更容易:var noDupes = new HashSet&lt;T&gt;(list); list.Clear(); list.AddRange(noDupes); :)【参考方案4】:

如果您不关心订单,您可以将项目推入HashSet,如果您确实想要保持订单,您可以执行以下操作:

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

或 Linq 方式:

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

编辑:HashSet 方法是O(N) 时间和O(N) 空间,同时排序然后使其唯一(如@lassevk 和其他人所建议的那样)是O(N*lgN) 时间和O(1) 空间,所以我不太清楚(乍一看)排序方式是劣等的(我为暂时的反对票道歉......)

【讨论】:

【参考方案5】:

怎么样:

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

在 .net 3.5 中?

【讨论】:

列表是否重复? @darkgaze 这只是创建了另一个只有唯一条目的列表。因此,所有重复项都将被删除,您会得到一个列表,其中每个位置都有不同的对象。 这是否适用于项目代码重复且需要获取唯一列表的列表项列表的列表【参考方案6】:

如果您使用的是 .Net 3+,则可以使用 Linq。

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

【讨论】:

该代码将失败,因为 .Distinct() 返回一个 IEnumerable。您必须将 .ToList() 添加到它。 这种方法只能用于简单值的列表。 不,它适用于包含任何类型对象的列表。但是您必须覆盖您的类型的默认比较器。像这样: public override bool Equals(object obj)... 用你的类覆盖 ToString() 和 GetHashCode() 总是一个好主意,这样这种事情就会起作用。 您还可以使用具有 .DistinctBy() 扩展方法的 MoreLinQ Nuget 包。很有用。【参考方案7】:

正如 kronoz 在 .Net 3.5 中所说,您可以使用 Distinct()

在 .Net 2 中你可以模仿它:

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;

这可用于对任何集合进行重复数据删除,并将按原始顺序返回值。

过滤集合(Distinct() 和本示例所做的)通常比从中删除项目要快得多。

【讨论】:

这种方法的问题在于它是 O(N^2)-ish,而不是哈希集。但至少它在做什么是显而易见的。 @DrJokepu - 实际上我没有意识到 HashSet 构造函数已经过重复数据删除,这使得它在大多数情况下更好。但是,这将保留排序顺序,HashSet 不会。 HashSet 在 3.5 中引入 @thorn 真的吗?很难跟踪。在这种情况下,您可以只使用Dictionary&lt;T, object&gt;,将.Contains 替换为.ContainsKey,将.Add(item) 替换为.Add(item, null) @Keith,根据我的测试,HashSet 保留了顺序,而 Distinct() 没有。【参考方案8】:

只需用相同类型的 List 初始化一个 HashSet:

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

或者,如果您想返回一个列表:

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

【讨论】:

... 如果您需要 List&lt;T&gt; 作为结果,请使用 new HashSet&lt;T&gt;(withDupes).ToList()【参考方案9】:

扩展方法可能是一个不错的方法......像这样:

public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)

    return listToDeduplicate.Distinct().ToList();

然后这样调用,例如:

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

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

这是一种原位删除相邻重复项的扩展方法。首先调用 Sort() 并传入相同的 IComparer。这应该比 Lasse V. Karlsen 的版本更有效,后者反复调用 RemoveAt(导致多次块内存移动)。

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

【讨论】:

【参考方案12】:

有很多方法可以解决 - 列表中的重复问题,下面是其中之一:

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

干杯 拉维甘尼桑

【讨论】:

【参考方案13】:

这是一个简单的解决方案,不需要任何难以阅读的 LINQ 或任何事先对列表进行排序。

   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
                
            
        
    

【讨论】:

您可以通过此方法更好地控制重复项。如果您有要更新的数据库,则更是如此。对于innerIndex,为什么不从outerIndex+1开始,而是每次都从头开始?【参考方案14】:

简单地确保不将重复项添加到列表中可能会更容易。

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

【讨论】:

我目前正在这样做,但是您拥有的条目越多,检查重复项的时间就越长。 我这里也有同样的问题。我每次都使用List&lt;T&gt;.Contains 方法,但有超过1,000,000 个条目。这个过程减慢了我的申请速度。我首先使用List&lt;T&gt;.Distinct().ToList&lt;T&gt;() 这个方法很慢【参考方案15】:

我喜欢用这个命令:

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

我的列表中有以下字段:Id、StoreName、City、PostalCode 我想在具有重复值的下拉列表中显示城市列表。 解决方案:按城市分组,然后选择列表中的第一个。

【讨论】:

这适用于我有多个项目具有相同密钥的情况,并且必须只保留最近更新日期的项目。所以使用“distinct”的方法是行不通的。【参考方案16】:

它对我有用。只需使用

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

将“类型”替换为您想要的类型,例如诠释。

【讨论】:

Distinct 在 Linq 中,而不是 MSDN 页面报告的 System.Collections.Generic。 此答案 (2012) 似乎与此页面上 2008 年的其他两个答案相同?【参考方案17】:

David J. 的回答是一个很好的方法,不需要额外的对象、排序等。但是可以改进:

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

因此,对于整个列表,外循环位于顶部底部,但内循环位于底部“直到到达外循环位置”。

外循环确保整个列表都被处理,内循环找到实际的重复项,这些只会发生在外循环尚未处理的部分。

或者,如果您不想为内部循环执行自下而上的操作,您可以让内部循环从 outerIndex + 1 开始。

【讨论】:

【参考方案18】:
  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++;
        
       
  

【讨论】:

【参考方案19】:

作为辅助方法(没有 Linq):

public static List<T> Distinct<T>(this List<T> list)

    return (new HashSet<T>(list)).ToList();

【讨论】:

我认为 Distinct 已经被采用了。除此之外(如果您重命名方法)它应该可以工作。【参考方案20】:

通过 Nuget 安装 MoreLINQ 包,您可以通过属性轻松区分对象列表

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

【讨论】:

【参考方案21】:

你可以使用联合

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

【讨论】:

解释为什么它会起作用肯定会让这个答案更好【参考方案22】:

使用 Linq 的 Union 方法。

注意:这个解决方案不需要 Linq 的知识,除了它存在。

代码

首先将以下内容添加到类文件的顶部:

using System.Linq;

现在,您可以使用以下方法从名为 obj1 的对象中删除重复项:

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

注意:将 obj1 重命名为您的对象的名称。

工作原理

    Union 命令列出两个源对象的每个条目之一。由于 obj1 都是源对象,因此这会将 obj1 简化为每个条目之一。

    ToList() 返回一个新列表。这是必要的,因为像 Union 这样的 Linq 命令将结果作为 IEnumerable 结果返回,而不是修改原始 List 或返回新 List。

【讨论】:

【参考方案23】:

一个简单直观的实现:

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;
    

【讨论】:

这种方法也很慢。创建一个新列表。【参考方案24】:

如果您有两个班级 ProductCustomer 并且我们想从他们的列表中删除重复的项目

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; 


您必须以下面的形式定义一个泛型类

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

然后,您可以删除列表中的重复项。

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

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

此代码通过Id 删除重复项如果您想通过其他属性删除重复项,您可以更改nameof(YourClass.DuplicateProperty) 相同的nameof(Customer.CustomerName) 然后通过CustomerName 属性删除重复项。

【讨论】:

【参考方案25】:

这需要 distinct(没有重复元素的元素)并再次将其转换为列表:

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

【讨论】:

【参考方案26】:

所有答案都复制列表,或者创建一个新列表,或者使用慢速函数,或者只是非常缓慢。

据我了解,这是我所知道的最快、最便宜的方法(还得到了一位非常有经验的专门从事实时物理优化的程序员的支持)。

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

最终费用为:

nlogn + n + nlogn = n + 2nlogn = O(nlogn) 这很不错。

关于 RemoveRange 的注意事项: 由于我们无法设置列表的计数并避免使用 Remove 函数,因此我不确切知道此操作的速度,但我想这是最快的方法。

【讨论】:

【参考方案27】:

我认为最简单的方法是:

创建一个新列表并添加唯一项。

例子:

        class MyList
    int id;
    string date;
    string email;
    
    
    List<MyList> ml = new Mylist();

ml.Add(new MyList()
id = 1;
date = "2020/09/06";
email = "zarezadeh@gmailcom"
);

ml.Add(new MyList()
id = 2;
date = "2020/09/01";
email = "zarezadeh@gmailcom"
);

 List<MyList> New_ml = new Mylist();

foreach (var item in ml)
                
                    if (New_ml.Where(w => w.email == item.email).SingleOrDefault() == null)
                    
                        New_ml.Add(new MyList()
                        
                          id = item.id,
     date = item.date,
               email = item.email
                        );
                    
                

【讨论】:

【参考方案28】:

使用HashSet 可以轻松完成。

List<int> listWithDuplicates = new List<int>  1, 2, 1, 2, 3, 4, 5 ;
HashSet<int> hashWithoutDuplicates = new HashSet<int> ( listWithDuplicates );
List<int> listWithoutDuplicates = hashWithoutDuplicates.ToList();

【讨论】:

【参考方案29】:

根据删除重复项,我们必须应用以下逻辑,以便快速删除重复项。

public class Program


    public static void Main(string[] arges)
    
        List<string> cities = new List<string>()  "Chennai", "Kolkata", "Mumbai", "Mumbai","Chennai", "Delhi", "Delhi", "Delhi", "Chennai", "Kolkata", "Mumbai", "Chennai" ;
        cities = RemoveDuplicate(cities);

        foreach (var city in cities)
        
            Console.WriteLine(city);
        
    

    public static List<string> RemoveDuplicate(List<string> cities)
    
        if (cities.Count < 2)
        
            return cities;
        

        int size = cities.Count;
        for (int i = 0; i < size; i++)
        
            for (int j = i+1; j < size; j++)
            
                if (cities[i] == cities[j])
                
                    cities.RemoveAt(j);
                    size--;
                    j--;
                
            
        
        return cities;
    

【讨论】:

【参考方案30】:

使用哈希集: list = new HashSet&lt;T&gt;(list).ToList();

【讨论】:

以上是关于从 C# 中的 List<T> 中删除重复项的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中枚举时从 List<T> 中删除项目的智能方法

从 C# 中的变量中删除双引号 [关闭]

如何从 C# 中的通用 List<T> 中获取元素? [复制]

从 C# 中的 List<T> 中选择 N 个随机元素

从 C# 中的 List<T> 中选择 N 个随机元素的算法[重复]

C# 从 List<T> 中选择存在于另一个列表中的所有元素