【问题标题】:Fast ways to avoid duplicates in a List<> in C#在 C# 中避免 List<> 中重复的快速方法
【发布时间】:2013-06-21 03:27:12
【问题描述】:

我的 C# 程序根据给定的模式生成随机字符串。这些字符串存储在一个列表中。由于不允许重复,所以我这样做:

List<string> myList = new List<string>();
for (int i = 0; i < total; i++) {
  string random_string = GetRandomString(pattern);
  if (!myList.Contains(random_string)) myList.Add(random_string);
}

您可以想象,这适用于数百个条目。但是我面临着生成数百万个字符串的情况。并且随着每个添加的字符串检查重复项变得越来越慢。

有没有更快的方法来避免重复?

【问题讨论】:

  • 将它们全部添加,然后使用 Distinct() 检查重复项,然后将删除的数字加回会更快吗?
  • @Jonesy:这听起来像是值得对特定数据集进行测试的东西。如果它确实更快,那么人们会权衡性能优化与它添加到代码中的混淆(在这种情况下并不多)。
  • @David 我可能会提出理论上的论点,即HashSet&lt;T&gt; 会更快,因为最初的内存影响较小,之后无需完全迭代。检查每个项目的成本仍然存在,但该数据结构已针对它进行了优化。
  • @Robert 你能为每个文档使用GUID 吗?
  • @musefan 进行单个数据库查询来确定数据库中是否已存在项目将花费超过数十万甚至数百万次检查以查看项目是否存在于内存中的哈希集中.使用 DB 来解决这个特定问题很容易导致数千倍的速度下降。

标签: c# list duplicates


【解决方案1】:

使用可以更有效地确定项目是否存在的数据结构,即HashSet。它可以在恒定时间内确定一个项目是否在集合中,而不管集合中有多少项目。

如果您真的需要 List 中的项目,或者您需要结果列表中的项目按照生成的顺序排列,那么您可以将数据存储在两个列表和哈希集;如果 HashSet 中当前不存在该项目,则将该项目添加到两个集合中。

【讨论】:

  • 好的,所以我使用了HashSet,速度的提升是巨大的。但是我确实有一个新问题。我需要哈希集中的一定数量的条目。如果我在我的问题中使用 for 循环,那么它会在 2,000,000 次循环后停止。哈希集中不存在重复项,但如果命中重复项,则哈希集没有 2,000,000 个条目。我怎么能避免呢? if (myList.Count &lt; 2000000) myList.Add(random_string); 阻止了这种情况,但还是有点慢。
  • @Robert 而不是for(int i = 0; i &lt; numItems; i++),只需使用for(int i = 0; set.Count &lt; numItems; i++)。或者,如果您实际上根本不需要i,那么只需while(set.Count &lt; numItems)
  • 看来,HasSet 的查找项目是 O(1),所以如果您找到此项目=将其添加到重复列表。
【解决方案2】:

最简单的方法是使用这个:

myList = myList.Distinct().ToList();

虽然这需要创建一次列表,然后再创建一个新列表。更好的方法可能是提前制作生成器:

public IEnumerable<string> GetRandomStrings(int total, string pattern)
{
    for (int i = 0; i < total; i++) 
    {
        yield return GetRandomString(pattern);
    }
}

...

myList = GetRandomStrings(total, pattern).Distinct().ToList();

当然,如果您不需要按索引访问项目,则可以通过删除ToList 并仅使用IEnumerable 来进一步提高效率。

【讨论】:

  • 使用.Distinct 删除列表中的数百万个字符串听起来并不那么高效。
  • @DarrenDavies 在内部,Distinct 使用 HashSet,正如其他人所建议的那样。唯一低效的部分是首先生成列表,然后使用 distinct,我在答案的第二部分中已解决。
  • 另外,如果结果中需要一些字符串,让GetRandomStrings 生成一个无限长的序列,然后使用Take 将其限制为所需的大小可能是有意义的.然后,您可以将 Take 放在 Distinct 之前或之后,具体取决于您是要指定生成的字符串数还是生成的 unique 字符串数。
  • @Servy 我最初是这样实现的,但是无限生成器可能很危险,需要小心处理。
【解决方案3】:

不要使用List&lt;&gt;。请改用Dictionary&lt;&gt;HashSet&lt;&gt;

【讨论】:

  • 通过使用 HashSet,您不能像使用 List 一样访问和更改对象。
【解决方案4】:

如果顺序不重要,您可以使用HashSet&lt;string&gt;

HashSet<string> myHashSet = new HashSet<string>();
for (int i = 0; i < total; i++) 
{
   string random_string = GetRandomString(pattern);
   myHashSet.Add(random_string);
}

HashSet 类提供高性能的集合操作。集合是不包含重复元素且其元素没有特定顺序的集合。

MSDN

或者如果顺序很重要,我建议使用SortedSet(仅限.net 4.5)

【讨论】:

  • 请注意,SortedSet&lt;T&gt; 对元素进行排序。如果需要有序集(即元素顺序保持不变)OrderedDictionary 将是更好的选择。缺点是不通用。
  • 那我如何得到散列对象呢? HashSet 没有 GET,实现自己的效率也不是很高。
【解决方案5】:

不是一个好方法,而是一种快速修复, 取一个布尔值来检查整个列表中是否有任何重复条目。

bool containsKey;
string newKey;

    public void addKey(string newKey){

         foreach(string key in MyKeys){
           if(key == newKey){
             containsKey = true;
          }
         }

      if(!containsKey){
       MyKeys.add(newKey);
     }else{
       containsKey = false;
     }

    }

【讨论】:

    【解决方案6】:

    Hashtable 比列表更快地检查项目是否存在。

    【讨论】:

    • 他没有键/值关系,只是一堆字符串,所以他需要一个集合而不是映射。此外,HashTable 不是通用的;如果您确实需要地图结构,则应该使用通用的Dictionary。您不应该在非遗留代码中使用 HashTable。
    【解决方案7】:

    你试过了吗:

    myList = myList.Distinct()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-21
      相关资源
      最近更新 更多