【问题标题】:Two strings for Key of a Dictionary字典键的两个字符串
【发布时间】:2011-06-30 21:28:06
【问题描述】:

我有两个字符串,我想将它们用作字典键,但我有点懒于创建另一个对象,计算字符串的哈希码等。

那么,我可以得到两个字符串的哈希码,添加它们并将结果用作字典的键吗?

有可能造成冲突吗?对吧?

有什么想法吗?

【问题讨论】:

  • 您使用的是哪个 .NET 版本?

标签: c# string data-structures dictionary hashcode


【解决方案1】:

我有两个字符串,我想 将它们用作字典键,但我 m 有点懒得再创建一个对象了

在 .NET 4.0 中,您可以使用 Tuple<T1, T2> 类作为键,其中 T1 和 T2 = 字符串。

我可以得到两个字符串的哈希码吗 , 添加它们并将结果用作 字典的键?

Tuple<T1, T2> 用于组合哈希码的公式类似于(未记录或保证不会更改):((h1 << 5) + h1) ^ h2,对于您的目的应该足够体面。顺便说一句,天真的添加通常不是组合哈希码的最佳方式。

有可能造成冲突吗? 对吧?

这总是可能的,即使是 single 字符串。字符串多于 32 位整数。

【讨论】:

    【解决方案2】:

    使用元组:

    var dict = new Dictionary<Tuple<string,string>,SomeType>();
    dict.Add(Tuple.Create("Hello","World"), new SomeType());
    

    【讨论】:

      【解决方案3】:

      如果您使用的是 .NET 4,则可以使用 Tuple 类:

      Dictionary<Tuple<string, string>, TValue> dict = new ...
      

      如果您不在 .NET 4 上,您应该创建自己的类型来保存它。

      您可以使用KeyValuePair 结构,但它从基值类型继承了相关方法,因此严重依赖反射。这会影响性能(请参阅答案底部。)

      对于键值对:

      Dictionary<KeyValuePair<string, string>, TValue> dict = new ...
      

      如果您不想自己做饭,可以使用以下通用类型:

      public struct SimpleTuple<TValue1, TValue2>
      {
          private readonly TValue1 _Value1;
          private readonly TValue2 _Value2;
      
          public SimpleTuple(TValue1 value1, TValue2 value2)
          {
              _Value1 = value1;
              _Value2 = value2;
          }
      
          public TValue1 Value1 { get { return _Value1; } }
          public TValue2 Value2 { get { return _Value2; } }
      
          public int GetHashCode()
          {
              unchecked
              {
                  int result = 37;
      
                  result *= 23;
                  if Value1 != null)
                      result += Value1.GetHashCode();
      
                  result *= 23;
                  if (Value2 != null)
                      result += Value2.GetHashCode();
      
                  return result;
              }
          }
      
          public override bool Equals(object obj)
          {
              if (obj == null) return false;
              if (obj.GetType() != typeof(SimpleTuple<TValue1, TValue2>))
                  return false;
      
              var other = (SimpleTuple<TValue1, TValue2>)obj;
              return Equals(other.Value1, Value1) && Equals(other.Value2, Value2);
          }
      }
      

      当然,KeyValuePair 也适用于 .NET 4.0,就像 good bad。

      至于碰撞,这取决于你的意思。哈希表(字典在内部使用哈希表结构)总是有可能发生键冲突,但这就是比较起作用的地方。如果两个不同的键生成相同的哈希码,字典类会将键与键进行比较,以查看它们是否真的是相同的值,或者只是产生相同的哈希码。

      为什么哈希表总是有可能发生冲突的原因最好用pidgeonhole principle (Wikipedia)来描述。

      这意味着,如果两个不同的键会导致冲突,这不会有问题,它们都会以正确的值存储在字典中。

      当然,如果您创建了两次相同的键,字典会将其视为相同的键,并且要么无法添加新值,要么覆盖现有的值(取决于您要求它添加值的方式。)

      这将在重复键上引发异常:

      dict.Add(key, value);
      

      这将添加或覆盖现有的:

      dict[key] = value;
      

      针对 Ani 的评论,我为LINQPad 编写了以下简单的测试脚本。输出是:

      键值对:975 毫秒 MyKeyValuePair:52ms

      脚本:

      void Main()
      {
          const int iterations = 10 * 1000 * 1000;
      
          // JIT preheat
          Test1(1);
          Test2(1);
      
          Stopwatch sw = Stopwatch.StartNew();
          Test1(iterations);
          sw.Stop();
          Debug.WriteLine("KeyValuePair: " + sw.ElapsedMilliseconds + "ms");
      
          sw = Stopwatch.StartNew();
          Test2(iterations);
          sw.Stop();
          Debug.WriteLine("MyKeyValuePair: " + sw.ElapsedMilliseconds + "ms");
      }
      
      public static void Test1(int iterations)
      {
          for (int index = 0; index < iterations; index++)
          {
              var kvp = new KeyValuePair<int, int>(index, index);
              kvp.GetHashCode();
          }
      }
      
      public static void Test2(int iterations)
      {
          for (int index = 0; index < iterations; index++)
          {
              var kvp = new MyKeyValuePair<int, int>(index, index);
              kvp.GetHashCode();
          }
      }
      
      public struct MyKeyValuePair<TKey, TValue>
      {
          private readonly TKey _Key;
          private readonly TValue _Value;
      
          public MyKeyValuePair(TKey key, TValue value)
          {
              _Key = key;
              _Value = value;
          }
      
          public TKey Key { get { return _Key; } }
          public TValue Value { get { return _Value; } }
      
          public int GetHashCode()
          {
              unchecked
              {
                  int result = 37;
      
                  result *= 23;
                  if (Key != null)
                      result += Key.GetHashCode();
      
                  result *= 23;
                  if (Value != null)
                      result += Value.GetHashCode();
      
                  return result;
              }
          }
      
          public override bool Equals(object obj)
          {
              if (obj == null) return false;
              if (obj.GetType() != typeof(MyKeyValuePair<TKey, TValue>))
                  return false;
      
              var other = (MyKeyValuePair<TKey, TValue>)obj;
              return Equals(other.Key, Key) && Equals(other.Value, Value);
          }
      }
      

      【讨论】:

      • 有使用KVP作为key的经验吗?我想知道性能会如何,考虑到相等性和哈希码计算应该来自System.ValueType,因为它似乎没有覆盖它们。
      • 我没有直接测量过这一点,但我也从未处于性能分析过程中字典是主要罪魁祸首的位置。它可能比使用特定方法手动编码类似类型要慢得多。让我这样做,然后回来编辑。
      • @Ani,你说对了,KeyValuePair 是一个糟糕的选择。我将编辑我的答案。
      • 您为什么没有为 Tuple 添加测试?您通过建议使用元组开始您的答案,但不要测量它。我做了一个类似于你为 KeyValuePair 做的测试,它的表现更差。
      • 我真的不记得了。如果您有更新的测试,请随时使用它来编辑我的答案。目前我正在病假,只能访问虚拟机来运行它,所以它不会很准确。
      【解决方案4】:

      简单的解决方案,适用于所有版本的 .net。只需将字符串连接在一起。

      var dictionary = new Dictionary<string, int>();
      dictionary.Add("The meaning" + " of life, the universe, and everything", 42);
      

      当然,这仅适用于 2 个字符串(尽管您可以在许多其他类型上使用 .ToString())并且如果您不需要仅通过两个字符串之一查找字典,但如果您同时拥有这两个字符串很简单。

      【讨论】:

      • 我会补充一点,如果两个字符串中没有包含某些字符,则此技术有效。例如姓名和姓氏。他们两个都不应该有 \n (新行)。所以 Name\nSurname 是“足够好”(请注意,一些狡猾的黑客可能会使用它来入侵您的网站!这将非常困难,但并非不可能)。考虑到许多系统都是基于 C 的,可能字符 \0 使用起来非常安全。 (或者您可以简单地转义您用来划分字符串的字符的任何出现,例如:Name.Replace("|", "||") + "|" + Surname
      猜你喜欢
      • 1970-01-01
      • 2013-08-08
      • 1970-01-01
      • 2021-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-14
      • 2019-05-01
      相关资源
      最近更新 更多