【问题标题】:C# dictionary type with unique keys and values具有唯一键和值的 C# 字典类型
【发布时间】:2012-03-15 08:01:54
【问题描述】:

我想知道 C# 中是否有类似“字典”的内置类型,但 TKey 和 TValue 都必须是唯一的。

例如::

d.Add(1, "1");
d.Add(2, "1"); // This would not be OK because "1" has already been used as a value.

我知道这有点奇怪,但似乎由于 BCL 中有大约十亿个集合类型,它可能存在。有什么想法吗?

【问题讨论】:

  • 使值成为键的一部分。
  • .NET Framework 中没有这样的类。但是您可以轻松地从 Dictionary 和 HashSet 或两个 Dictionaries 中构造一个。
  • @Robert Harvey:如果他这样做了,他就不能再做d[1],这违背了字典的目的。还不如使用 HashSet
  • @RobertHarvey:如果你只有一半的密钥,你会如何找回另一半?
  • stackoverflow.com/a/5853223/59303 - 你必须存储一个额外的charTuple 可能对你有用。尽管这确实遇到了与其他 cmets 中提到的相同的问题。

标签: c# collections dictionary


【解决方案1】:

如果给定值已经存在,您可以实现一个扩展方法,该方法跳过添加到字典中。

static class Extensions
{
    public static void AddSafe(this Dictionary<int, string> dictionary, int key, string value)
    {
        if (!dictionary.ContainsValue(value))
            dictionary.Add(key, value);
    }
}

像普通函数一样调用它

d.AddSafe(1, "1");
d.AddSafe(2, "1"); // This would not add anything

【讨论】:

    【解决方案2】:

    出于内部目的,我写了一个BiDictionary。它不是防弹的,因为我不会将它暴露给用户,所以它对我来说很好用。它让我可以根据需要获取任一密钥。

    KeyPair&lt;,&gt; 是实现IEnumerable&lt;,&gt;Add 方法所必需的,这样我们才能使用对象初始化器。

    internal class KeyPair<TKey1, TKey2>
    {
        public TKey1 Key1 { get; set; }
        public TKey2 Key2 { get; set; }
    }
    

    这是作为动态对象的主类,因此我们可以在检索值时在其上使用键名:

    internal class BiDictionary<TKey1, TKey2> : DynamicObject, IEnumerable<KeyPair<TKey1, TKey2>>
    {
        private readonly Dictionary<TKey1, TKey2> _K1K2 = new Dictionary<TKey1, TKey2>();
        private readonly Dictionary<TKey2, TKey1> _K2K1 = new Dictionary<TKey2, TKey1>();
    
        private readonly string _key1Name;
        private readonly string _key2Name;
    
        public BiDictionary(string key1Name, string key2Name)
        {
            _key1Name = key1Name;
            _key2Name = key2Name;
        }
    
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (binder.Name == _key1Name)
            {
                result = _K1K2;
                return true;
            }
    
            if (binder.Name == _key2Name)
            {
                result = _K2K1;
                return true;
            }
    
            result = null;
            return false;
        }
    
        public void Add(TKey1 key1, TKey2 key2)
        { 
            _K1K2.Add(key1, key2);
            _K2K1.Add(key2, key1);
        }
    
        public IEnumerator<KeyPair<TKey1, TKey2>> GetEnumerator()
        {
            return _K1K2.Zip(_K2K1, (d1, d2) => new KeyPair<TKey1, TKey2>
            {
                Key1 = d1.Key,
                Key2 = d2.Key
            }).GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
    

    例子:

    dynamic bidic = new BiDictionary<string, string>("Key1", "Key2") 
    { 
        { "foo", "bar" }, 
        { "baz", "qux" } 
    };
    var bar = bidic.Key1["foo"];
    var foo = bidic.Key2["bar"];
    

    如果您在外面修改任何字典,它们可能会不同步。为此,我使用ObservableDictionary,以便在其中一个发生更改时更新另一个,但为了简单起见,我删除了这部分代码以演示主要逻辑。

    【讨论】:

      【解决方案3】:

      我通过将数据存储为Dictionary&lt;TKey, HashSet&lt;TValue&gt;&gt; 解决了这个问题。 如果你想要一个有 2 个主键的值,你可以用另一个 Dictionary 替换 HashSet。

      Dictionary<int, HashSet<int>> _myUniquePairOfIntegerKeys;
      // OR
      Dictionary<string, Dictionary<string, bool>> _myUniquePairOfStringKeysWithABooleanValue;
      

      【讨论】:

        【解决方案4】:

        有一个位于here 的项目具有这样的类型。它被称为 PairDictionary,它工作得很好。不是最佳答案,但适合任何需要该自定义类的人。

        【讨论】:

        • PairDictionary 是一个糟糕的解决方案。在内部它与列表一起使用,每个操作都是 O(n) 操作,不像 real 字典的 O(1) --- source
        • @t3chb0t 您关注的是实现而不是 API。是的,它不是最好的,但它的使用才是最重要的。由于这是一些开源代码的一部分,你应该提交一个改进功能的补丁:)
        【解决方案5】:

        拥有 Dictionary 和 HashSet/secondary reverse Dictionary 怎么样 - 它将解决问题,并且比单个 Dictionary 的检查性能更好。

        类似这样的东西,包装成类:

        HashSet<string> secondary = new HashSet<string>(/*StringComparer.InvariantCultureIgnoreCase*/);
        Dictionary<int, string>dictionary = new Dictionary<int, string>();
        object syncer = new object();
        
        public override void Add(int key, string value)
        {
          lock(syncer)
          {
            if(dictionary.ContainsKey(key))
            {
              throw new Exception("Key already exists");
            }
        
            if(secondary.Add(value)
            {
              throw new Exception("Value already exists");
            }
            dictionary.Add(key, value);
          }
        }
        

        【讨论】:

        • 然后将其包装在自定义类中。
        • @Jason 如果你需要它超过 1 次 - 绝对是的!
        • @OlegDok 如果您必须阅读代码超过 1 次 - 绝对可以! (而且您应该总是希望有一天必须再次阅读此代码。)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-31
        • 2021-12-19
        • 1970-01-01
        • 2020-09-12
        相关资源
        最近更新 更多