【问题标题】:HashSet allows duplicate item insertion - C#HashSet 允许重复项插入 - C#
【发布时间】:2012-02-03 13:50:51
【问题描述】:

这种似乎是一个菜鸟问题,但我找不到这个问题的具体答案。

我有这门课:

public class Quotes{ 
    public string symbol; 
    public string extension
}

我正在使用这个:

HashSet<Quotes> values = new HashSet<Quotes>();

但是,我可以多次添加相同的 Quotes 对象。例如,我的 Quotes 对象可能有 'symbol' 等于 'A' 和 'extension' 等于 '=n',并且这个 Quotes 对象在 HashSet 中出现多次(通过调试模式查看 Hashset)。打电话的时候我也想过

values.Add(new Quotes(symb, ext));

使用相同的符号和分机,将返回'false'并且不会添加元素。我感觉它与 HashSet 添加新对象时比较 Quotes 对象有关。任何帮助将不胜感激!

【问题讨论】:

  • 也许你想看看 HashTable 甚至更好的 Dictionary
  • @jpints14 你在讨论什么?字符串内容还是内存位置? (或其他)
  • “能够多次添加相同的 Quotes 对象”是指添加完全相同的实例,还是添加相同的实例?

标签: c# .net hashset


【解决方案1】:

我猜您正在创建一个具有相同值的新Quotes。在这种情况下,它们不相等。如果它们应该被视为相等,请覆盖 Equals 和 GetHashCode 方法。

public class Quotes{ 
    public string symbol; 
    public string extension

    public override bool Equals(object obj)
    {
        Quotes q = obj as Quotes;
        return q != null && q.symbol == this.symbol && q.extension == this.Extension;
    }

    public override int GetHashCode()
    {
        return this.symbol.GetHashCode() ^ this.extension.GetHashCode();
    }
}

【讨论】:

  • 请注意,如果符号或扩展名可能为空,则 GetHashCode 必须处理该问题并且不会崩溃。
  • 在需要比较之前我确实有一个检查,但感谢您的提示
  • 注意,对于strings、ints或其他值类型或密封类以外的字段类型,应使用q != null &amp;&amp; q.symbol.Equals(this.symbol) &amp;&amp; q.extension.Equals(this.extension)而不是==,因为==是不是多态的(即如果子类定义了operator ==,基类'orperator == 仍将被使用,而子类可以覆盖.Equals() 方法,因此子类'.Equals() 将是使用过。另外,hash1 ^ hash2 是一个糟糕的哈希实现,因为 "a", "b""b", "a" 具有相同的哈希。更喜欢像 (hash1 + 7 * 13) ^ hash2 这样的东西。
  • 我刚刚覆盖了 Equals 和 GetHashCode,就像本例中所示,但在我的情况下,我仍然在 HashSet 中得到重复项。为什么?
【解决方案2】:

我原以为用相同的symb和ext调用values.Add(new Quotes(symb, ext));时,会返回'false'并且不会添加元素。

事实并非如此。

HashSet 将使用GetHashCodeEquals 来确定对象的相等性。现在,由于您没有在Quotes 中覆盖这些方法,因此将使用默认的System.Object 的引用相等性。每次您添加一个新的 Quote,它都是一个唯一的对象实例,因此 HashSet 将其视为一个唯一的对象。

如果您覆盖 Object.EqualsObject.GetHashCode,它将按预期工作。

【讨论】:

    【解决方案3】:

    HashSets 首先根据 GetHashCode 计算的哈希值比较条目。
    默认实现根据对象本身返回一个哈希码(每个实例之间不同)。

    只有当哈希值相同时(基于实例的哈希值非常不可能),才会调用 Equals 方法并用于明确比较两个对象。

    你必须选择:

    • 将引号更改为结构
    • 在引号中覆盖 GetHashCode 和 Equals

    例子:

     public override int GetHashCode()
     {
        return (this.symbol == null ? 0 : this.symbol.GetHashCode())
           ^ (this.extension == null ? 0 : this.extension.GetHashCode());
     }
     public override bool Equals(object obj)
     {
        if (Object.ReferenceEquals(this, obj))
          return true;
    
        Quotes other = obj as Quotes;
        if (Object.ReferenceEquals(other, null))
          return false;
    
        return String.Equals(obj.symbol, this.symbol)
            && String.Equals(obj.extension, this.extension);
     }
    

    【讨论】:

    • 您还需要覆盖Object.Equals - 不保证哈希是唯一的,因此两种方法都使用...
    • 是的 - 专注于足够快地写出答案:-D 我刚刚添加了它,谢谢。
    • mmm - 我认为您的 Object.ReferenceEquals 检查不太正确... ;) 基本上,按照您的方式,任何时候“obj”都是 Quotes 对象,您会说它不相等(这是它永远相等的唯一方法......)
    • 啊!当打字时两个 if 合二为一时,就会发生这种情况……看来我该休息一下了 :-)
    • hash1 ^ hash2 是一个糟糕的哈希实现,因为 "a", "b""b", "a" 具有相同的哈希。考虑类似(hash1 + 7 * 13) ^ hash2
    【解决方案4】:

    只是想修复 Kendall 的回答中的一些问题(由于某些奇怪的原因无法发表评论)。

    return this.symbol.GetHashCode() ^ this.extension.GetHashCode();
    

    请注意,xor 函数是组合两个散列的一种特别容易发生冲突的方法,尤其是当它们都是相同类型时(因为符号 == 扩展的每个对象都将散列为 0)。即使它们不是同一类型或不太可能彼此相等,这也是不好的做法,并且习惯它可能会导致不同设备出现问题。

    相反,将一个散列乘以一个小的质数,然后加上第二个,例如:

    return 3 * this.symbol.GetHashCode() + this.extension.GetHashCode();
    

    【讨论】:

      【解决方案5】:

      我知道这有点晚了,但我遇到了同样的问题,并在实施所选答案时发现了不可接受的性能损失,尤其是当您有很多记录时。

      我发现使用 Hashset 和 Tuple 将其转变为两步过程并最终通过 Select 进行转换的速度要快得多。

      public class Quotes{ 
          public string symbol; 
          public string extension
      }
      
      var values = new HashSet<Tuple<string,string>>();
      
      values.Add(new Tuple<string,string>("A","=n"));
      values.Add(new Tuple<string,string>("A","=n"));
      
      // values.Count() == 1
      
      values.Select (v => new Quotes{ symbol = v.Item1, extension = v.Item2 });
      

      【讨论】:

      • 尝试将其与接受的答案之类的方法进行比较,但也让Quotes 实现IEquatable&lt;Quotes&gt;,您可能会得到更好的结果。通过进一步调整 GetHashCode() 可能会获得更好的结果。
      【解决方案6】:
      Quotes q = new Quotes() { symbol = "GE", extension = "GElec" };
      values.Add(q);
      values.Add(q);
      

      .. 两次添加同一个实例,第二次返回 false。

      values.Add(new Quotes() { symbol = "GE", extension = "GElec" });
      values.Add(new Quotes() { symbol = "GE", extension = "GElec" });
      

      .. 正在添加两个不同的实例,它们恰好具有相同的公共字段值。

      如其他地方所述,覆盖 Equals 和 GetHashCode 将更正此问题:

      public class Quotes { 
          public string symbol; 
          public string extension;
      
          public override bool Equals(object obj) {
              if (!(obj is Quotes)) { return false; }
              return (this.symbol == ((Quotes)obj).symbol) && 
                     (this.extension == ((Quotes)obj).extension);
          }
      
          public override int GetHashCode() {
              return (this.symbol.GetHashCode()) ^ (this.extension.GetHashCode());
          }
      } 
      

      如果您逐步调试代码,您会发现 values.Add 调用了 Quotes.Equals 和 Quotes.GetHashCode。

      【讨论】:

      • ^ 在您的 return (this.symbol.GetHashCode()) ^ (this.extension.GetHashCode()); 中做了什么?这是我第一次看到这是一个错字吗?
      【解决方案7】:

      我被告知重写 Equals() 和 GetHashCode() 不是一个好习惯。

      类是引用类型,结构是值类型。更改为结构将允许按值进行相等比较,从而将相同的符号/扩展呈现为相等。

      public struct Quotes { 
          public string symbol; 
          public string extension;
      }
      
      public static void Main()
      {
          var hashSet = new HashSet<Quotes>();
      
          hashSet.Add(new Quotes { symbol = "aaa", extension = "bbb" });
          hashSet.Add(new Quotes { symbol = "aaa", extension = "bbb" });
      
          Console.WriteLine(hashSet.Count);
      }
      

      输出为 1。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-04-08
        • 1970-01-01
        • 2020-04-05
        • 1970-01-01
        • 1970-01-01
        • 2013-01-06
        • 2014-12-12
        相关资源
        最近更新 更多