【问题标题】:Multi-key dictionaries (of another kind) in C#?C# 中的多键字典(另一种)?
【发布时间】:2023-03-24 23:14:01
【问题描述】:

this question 的基础上,是否有一个简单的解决方案来拥有一个多键字典,其中单独的一个键 可以用来识别值?

即。

MultikeyDictionary<TKey1, TKey2, TValue> foo;
foo.Add(key1, key2, value);
myValue = foo[key1];
// value == myValue
foo.Remove(key2);
myValue = foo[key1]; // invalid, Exception or null returned

【问题讨论】:

    标签: c# data-structures


    【解决方案1】:

    不是直接的解决方案,也不适用于多键,但适用于我的用例。

    Dictionary<Guid, Object> objs = new Dictionary<Guid, Object>();
    Dictionary<int, Guid> guids = new Dictionary<int, Guid>();
    
    private void Set(object sender, Object obj)
    {
        objs[obj.Guid] = obj;
        guids[obj.Id] = obj.Guid;
    }
    
    public Object Get(int id)
    {
        return guids.ContainsKey(id) ? Get(guids[id]) : null;
    }
    
    public Object Get(Guid guid)
    {
        return objs.ContainsKey(guid) ? objs[guid] : null;
    }
    

    【讨论】:

      【解决方案2】:

      This blog post 似乎详细介绍了一个相当不错的实现。

      C#的多键通用字典类

      MultiKeyDictionary 是一个 C# 类 包装和扩展通用 字典对象由提供 .NET 2.0 及更高版本中的 Microsoft。这 允许开发人员创建一个泛型 值字典和参考 通过两个键而不是 只是微软提供的那个 通用的实施 字典<...>。你可以看到我的 但是,关于 CodeProject 的文章(此处) 此代码是最新的和错误的 免费。

      【讨论】:

      • 不仅当 k1 和 k2 是同一类型时,此方法不会编译,它还支持 3 个字典 - 这意味着需要两次查找才能在最坏的情况下获取值。这种方法唯一更快的领域是加法。
      • 我不确定nawfal在说什么,上面的例子是最快的全能,但这里是多个“多键”字典实现之间性能测试的更新版本:here
      • @JeffBridgman:试试这个链接(原始缓存):web.archive.org/web/20111209074636/http://www.aronweiler.com/…
      • 链接应该指向这里:fyslexicduck.com/2009/02/…
      【解决方案3】:

      我发现这里的许多答案都不必要地复杂、性能较差或根本无法使用。最好的方法是将辅助键的KeyValuePair&lt;&gt; 和值组合在一起作为任一字典的Value。这使您只需查找一次即可进行删除和更新操作。一个简单的实现:

      public class DualDictionary<TKey1, TKey2, TValue> : IEnumerable<KeyValuePair<Tuple<TKey1, TKey2>, TValue>>
      {
          Dictionary<TKey1, KeyValuePair<TKey2, TValue>> _firstKeys;
          Dictionary<TKey2, KeyValuePair<TKey1, TValue>> _secondKeys;
      
          public int Count
          {
              get
              {
                  if (_firstKeys.Count != _secondKeys.Count)
                      throw new Exception("somewhere logic went wrong and your data got corrupt");
      
                  return _firstKeys.Count;
              }
          }
      
          public ICollection<TKey1> Key1s
          {
              get { return _firstKeys.Keys; }
          }
      
          public ICollection<TKey2> Key2s
          {
              get { return _secondKeys.Keys; }
          }
      
          public IEnumerable<TValue> Values
          {
              get { return this.Select(kvp => kvp.Value); }
          }
      
          public DualDictionary(IEqualityComparer<TKey1> comparer1 = null, IEqualityComparer<TKey2> comparer2 = null)
          {
              _firstKeys = new Dictionary<TKey1, KeyValuePair<TKey2, TValue>>(comparer1);
              _secondKeys = new Dictionary<TKey2, KeyValuePair<TKey1, TValue>>(comparer2);
          }
      
      
      
          public bool ContainsKey1(TKey1 key)
          {
              return ContainsKey(key, _firstKeys);
          }
      
          private static bool ContainsKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
          {
              return dict.ContainsKey(key);
          }
      
          public bool ContainsKey2(TKey2 key)
          {
              return ContainsKey(key, _secondKeys);
          }
      
          public TValue GetValueByKey1(TKey1 key)
          {
              return GetValueByKey(key, _firstKeys);
          }
      
          private static TValue GetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
          {
              return dict[key].Value;
          }
      
          public TValue GetValueByKey2(TKey2 key)
          {
              return GetValueByKey(key, _secondKeys);
          }
      
          public bool TryGetValueByKey1(TKey1 key, out TValue value)
          {
              return TryGetValueByKey(key, _firstKeys, out value);
          }
      
          private static bool TryGetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, out TValue value)
          {
              KeyValuePair<T, TValue> otherPairing;
              bool b = TryGetValue(key, dict, out otherPairing);
              value = otherPairing.Value;
              return b;
          }
      
          private static bool TryGetValue<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict,
                                                out KeyValuePair<T, TValue> otherPairing)
          {
              return dict.TryGetValue(key, out otherPairing);
          }
      
          public bool TryGetValueByKey2(TKey2 key, out TValue value)
          {
              return TryGetValueByKey(key, _secondKeys, out value);
          }
      
          public bool Add(TKey1 key1, TKey2 key2, TValue value)
          {
              if (ContainsKey1(key1) || ContainsKey2(key2))   // very important
                  return false;
      
              AddOrUpdate(key1, key2, value);
              return true;
          }
      
          // dont make this public; a dangerous method used cautiously in this class
          private void AddOrUpdate(TKey1 key1, TKey2 key2, TValue value)
          {
              _firstKeys[key1] = new KeyValuePair<TKey2, TValue>(key2, value);
              _secondKeys[key2] = new KeyValuePair<TKey1, TValue>(key1, value);
          }
      
          public bool UpdateKey1(TKey1 oldKey, TKey1 newKey)
          {
              return UpdateKey(oldKey, _firstKeys, newKey, (key1, key2, value) => AddOrUpdate(key1, key2, value));
          }
      
          private static bool UpdateKey<S, T>(S oldKey, Dictionary<S, KeyValuePair<T, TValue>> dict, S newKey,
                                              Action<S, T, TValue> updater)
          {
              KeyValuePair<T, TValue> otherPairing;
              if (!TryGetValue(oldKey, dict, out otherPairing) || ContainsKey(newKey, dict))
                  return false;
      
              Remove(oldKey, dict);
              updater(newKey, otherPairing.Key, otherPairing.Value);
              return true;
          }
      
          public bool UpdateKey2(TKey2 oldKey, TKey2 newKey)
          {
              return UpdateKey(oldKey, _secondKeys, newKey, (key1, key2, value) => AddOrUpdate(key2, key1, value));
          }
      
          public bool UpdateByKey1(TKey1 key, TValue value)
          {
              return UpdateByKey(key, _firstKeys, (key1, key2) => AddOrUpdate(key1, key2, value));
          }
      
          private static bool UpdateByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, Action<S, T> updater)
          {
              KeyValuePair<T, TValue> otherPairing;
              if (!TryGetValue(key, dict, out otherPairing))
                  return false;
      
              updater(key, otherPairing.Key);
              return true;
          }
      
          public bool UpdateByKey2(TKey2 key, TValue value)
          {
              return UpdateByKey(key, _secondKeys, (key1, key2) => AddOrUpdate(key2, key1, value));
          }
      
          public bool RemoveByKey1(TKey1 key)
          {
              return RemoveByKey(key, _firstKeys, _secondKeys);
          }
      
          private static bool RemoveByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> keyDict,
                                                Dictionary<T, KeyValuePair<S, TValue>> valueDict)
          {
              KeyValuePair<T, TValue> otherPairing;
              if (!TryGetValue(key, keyDict, out otherPairing))
                  return false;
      
              if (!Remove(key, keyDict) || !Remove(otherPairing.Key, valueDict))
                  throw new Exception("somewhere logic went wrong and your data got corrupt");
      
              return true;
          }
      
          private static bool Remove<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
          {
              return dict.Remove(key);
          }
      
          public bool RemoveByKey2(TKey2 key)
          {
              return RemoveByKey(key, _secondKeys, _firstKeys);
          }
      
          public void Clear()
          {
              _firstKeys.Clear();
              _secondKeys.Clear();
          }
      
          public IEnumerator<KeyValuePair<Tuple<TKey1, TKey2>, TValue>> GetEnumerator()
          {
              if (_firstKeys.Count != _secondKeys.Count)
                  throw new Exception("somewhere logic went wrong and your data got corrupt");
      
              return _firstKeys.Select(kvp => new KeyValuePair<Tuple<TKey1, TKey2>, TValue>(Tuple.Create(kvp.Key, kvp.Value.Key),
                                                                                            kvp.Value.Value)).GetEnumerator();
          }
      
          IEnumerator IEnumerable.GetEnumerator()
          {
              return GetEnumerator();
          }
      }
      

      注意事项:

      1. 我只实现了IEnumerable&lt;&gt;。我认为ICollection&lt;&gt; 在这里没有意义,因为对于这种特殊的集合结构,方法名称都可能完全不同。由您决定 IEnumerable&lt;&gt; 里面应该放什么。

      2. 我试图在这里和那里抛出一些奇怪的异常 - 只是为了数据完整性。只是为了更安全,以便您知道我的代码是否有错误。

      3. 我以这样一种方式命名方法,即使Key1Key2 属于同一类型,它也可以编译。

      4. 性能:您可以使用Keys 中的任何一个查找ValueGetContains 方法只需要 1 次查找 (O(1))。 Add 需要 2 次查找和 2 次添加。 Update 需要 1 次查找和 2 次添加。 Remove 需要 3 次查找。

      【讨论】:

        【解决方案4】:

        另一个简单(有效)的实现是使用 PowerCollections' Pair&lt;TFirst, TSecond&gt; 类型作为字典键,类似于

        Dictionary<Pair<TKey1, TKey2>, TValue> foo;
        foo.Add(new Pair<TKey1, TKey2>(key1, key2), value);
        

        Pair 始终如一地实现 EqualsGetHashCode,因此您无需求助于多级字典(这更麻烦且可能效率较低)。

        如果您需要 3 键字典,还有一个 Triple&lt;TFirst, TSecond, TThird&gt;

        【讨论】:

        • 如果我只提供TKey2,我会得到TValue吗?
        • 这不能回答问题。该问题要求一个通过 either 键检索值的字典。这个解决方案总是需要两个键。
        【解决方案5】:

        我试过了,效果很好(包括添加、删除和索引器)

        public class MultikeyDictionary<K1, K2, V> : Dictionary<KeyValuePair<K1, K2>, V>
        {
            public V this[K1 index1, K2 index2]
            {
                get
                {
                    return this[new KeyValuePair<K1, K2>(index1, index2)];
                }
                set
                {
                    this[new KeyValuePair<K1, K2>(index1, index2)] = value;
                }
            }
        
            public bool Remove(K1 index1, K2 index2)
            {
                return base.Remove(new KeyValuePair<K1,K2>(index1, index2));
            }
        
            public void Add(K1 index1, K2 index2, V value)
            {
                base.Add(new KeyValuePair<K1, K2>(index1, index2), value);
            }
        }
        

        甚至我将其扩展为 4 个值:

        public class MultikeyDictionary<K1, K2, K3, V> : MultikeyDictionary<KeyValuePair<K1, K2>, K3, V>
        {
            public V this[K1 index1, K2 index2, K3 index3]
            {
                get
                {
                    return base[new KeyValuePair<K1, K2>(index1, index2), index3];
                }
                set
                {
                    base[new KeyValuePair<K1, K2>(index1, index2), index3] = value;
                }
            }
        
            public bool Remove(K1 index1, K2 index2, K3 index3)
            {
                return base.Remove(new KeyValuePair<K1, K2>(index1, index2), index3);
            }
        
            public void Add(K1 index1, K2 index2, K3 index3, V value)
            {
                base.Add(new KeyValuePair<K1, K2>(index1, index2), index3, value);
            }
        }
        

        享受,

        办公室

        【讨论】:

        • 如何回答这个问题?来自 OP 的问题:可以单独使用任一键来识别
        【解决方案6】:

        您可能会发现我的IndexMap 实现是一个很好的基础,可以将它从Java 重写为C#。编程模型不像我希望的那样优雅,但它并不意味着直接开发。相反,它位于提供标准注释以允许简洁编码风格的缓存库后面。通过使用 Map 接口,当它与自填充、过期和可驱逐的地图装饰器结合时,它提供了一个干净的组合模型。我相信有人会想出一个很好的编程接口,可以直接使用,而失去 Map 接口的好处是可以接受的。

        【讨论】:

          【解决方案7】:

          您将无法为这两种类型定义重载,并且泛型系统不允许任意数量的类型(例如方法允许参数)。因此,您会遇到一组定义 2、3、4 等同时键的类。此外,您必须使用 object 作为 get 和 set 的参数,使用运行时类型检查来模拟重载。

          此外,您只需要存储&lt;TKEY1,VAL&gt; 的一个字典,其他字典将是&lt;TKEY2,TKEY1&gt;&lt;TKEY3,TKEY1&gt; 并充当主字典的索引。

          主要是样板代码。

          【讨论】:

          • 我只需要 2 种密钥类型。这不是问题。
          【解决方案8】:

          是的,定义一个类,将对象添加到具有两个键的内部哈希表中,

           public MyClass<k1, k2, T>: Dictionary<object, T>
            {
                private Dictionary<k1, k2> keyMap;
                public new Add(k1 key1Val, k2 key2Val, T object)
                {
                   keyMap.Add(key1Val, key2Val);
                   base.Add(k2, object)
                }
                public Remove(k1 key1Val) 
                { 
                    base.Remove(keyMap[key1Val]); 
                    keyMap.Remove(key1Val);
                }
                public Remove(k2 key2Val) 
                { 
                  base.Remove(key2Val);
                  keyMap.Remove(key2Val);
                }
            }
          

          【讨论】:

          • (但如前所述,您不能在此实现中使用相同的类型)。
          • 第二种Remove方法不可行。 Remove 是如何在 keyMap 上完成的,其密钥是 k1,但您提供的是 k2?简而言之,这种方法并不完整。
          【解决方案9】:

          目前 .NET BCL 中没有为此类集合内置任何内容。

          我看到两个选项:

          1. 使用两级字典。第一级将不同的键映射到一些通用的唯一键(比如一个 GUID),第二级将 GUID 映射到实际值。

          2. 创建一个自定义键类并实现 Equals() 和 GetHashCode() 以便键的任何一个组件都足以找到整个键。然后,您可以提供帮助方法来仅使用其中一个值来构造键的实例,以便进行查找。

          【讨论】:

          • 1 是一个非常聪明的想法。我想到了 2,但我认为你会在 GetHashCode 上遇到一堵砖墙。它需要为 {1,1}、{2,1}、{1,2} 返回相同的值,但不是 {2,2}(尽管如果原始键是 {2,2},则它需要返回。 )
          • GetHashCode 不需要返回唯一值,但它的值确实需要与您的 Equals 值一致。您的对象可能只返回 0,节省大量计算时间,但当集合尝试使用您(非常)天真的哈希码时会浪费很多时间。
          • @Ryan - 我会实现 GetHashCode() 来对其中一个值进行哈希处理。哈希码不必是唯一的(正如 Matthew 所指出的那样)——它们只是帮助字典最大限度地减少冲突,从而影响性能。
          • 1 不是一个好主意。请参阅此处stackoverflow.com/a/1504548/661933 的相关讨论。我的意思是有更多性能更好的替代品
          【解决方案10】:

          当然,它是一种 OO 语言,您可以实现任何您想要的 O。您将有一些歧义需要解决(如果 TKey1 和 TKey2 是同一类型,那么会调用哪些方法?)

          【讨论】:

          • 我实际上不确定 C# 编译器如何(或是否)解决这个问题......有趣的一点。
          • 这是一个编译时错误:类型 'Test' 已经定义了一个名为 'x' 的成员具有相同的参数类型
          猜你喜欢
          • 1970-01-01
          • 2011-01-07
          • 2013-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-19
          • 1970-01-01
          相关资源
          最近更新 更多