.NET dictionary with two keys and one value

前端 未结 13 2950
盖世英雄少女心
盖世英雄少女心 2020-12-14 00:23

Is there a dictionary available in .NET that could hold 2 keys and one value. Like

Dictionary(Of TKey, Of TKey, TValue)

I have

相关标签:
13条回答
  • 2020-12-14 00:23

    As suggested in a comment to your question you could simply use an Object key for your Dictionary:

    Dictionary<Object, long> dict = new Dictionary<Object, long>();
    dict.Add("abc", 111);
    dict.Add(345, 111);
    

    To get a cleaner solution you could wrap this dictionary in a custom class and create your version of Add method:

    public void Add(ISet<Object> keys, T value){
        foreach(Object k in keys)
        {
            _privateDict.Add(k, value); 
        }
    }
    
    0 讨论(0)
  • 2020-12-14 00:23

    How about a Dictionary<Tuple<string, long>, long>? Tuples are compared by value, so it should index uniquely in the expected manner. Plus, now you won't have to pack the long value in two places (and deal with the wonderful pain of synchronizing the values everywhere).

    How about this approach? Basically, still use a dictionary-based strategy, but facade it through a class with overloaded indexer properties. So it looks like a dictionary, feels like a dictionary, but supports multiple keys (not like a dictionary, LOL).

    public class MultiKeyDictionary<TFirstKey, TSecondKey, TValue>
    {
        private readonly Dictionary<TFirstKey, TValue> firstKeyDictionary = 
            new Dictionary<TFirstKey, TValue>();
        private readonly Dictionary<TSecondKey, TFirstKey> secondKeyDictionary = 
            new Dictionary<TSecondKey, TFirstKey>();
    
        public TValue this[TFirstKey idx]
        {
            get
            {
                return firstKeyDictionary[idx];
            }
            set
            {
                firstKeyDictionary[idx] = value;
            }
        }
    
        public TValue this[TSecondKey idx]
        {
            get
            {
                var firstKey = secondKeyDictionary[idx];
                return firstKeyDictionary[firstKey];
            }
            set
            {
                var firstKey = secondKeyDictionary[idx];
                firstKeyDictionary[firstKey] = value;
            }
        }
    
        public IEnumerable<KeyValuePair<TFirstKey, TValue>> GetKeyValuePairsOfFirstKey()
        {
            return firstKeyDictionary.ToList();
        }
    
        public IEnumerable<KeyValuePair<TSecondKey, TValue>> GetKeyValuePairsOfSecondKey()
        {
            var r = from s in secondKeyDictionary
                join f in firstKeyDictionary on s.Value equals f.Key
                select new KeyValuePair<TSecondKey, TValue>(s.Key, f.Value);
    
            return r.ToList();
        }
    
        public void Add(TFirstKey firstKey, TSecondKey secondKey, TValue value)
        {
            firstKeyDictionary.Add(firstKey, value);
            secondKeyDictionary.Add(secondKey, firstKey);
        }
    
        public bool Remove(TFirstKey firstKey)
        {
            if (!secondKeyDictionary.Any(f => f.Value.Equals(firstKey))) return false;
    
            var secondKeyToDelete = secondKeyDictionary.First(f => f.Value.Equals(firstKey));
    
            secondKeyDictionary.Remove(secondKeyToDelete.Key);
            firstKeyDictionary.Remove(firstKey);
    
            return true;
        }
    
        public bool Remove(TSecondKey secondKey)
        {
            if (!secondKeyDictionary.ContainsKey(secondKey)) return false;
    
            var firstKey = secondKeyDictionary[secondKey];
            secondKeyDictionary.Remove(secondKey);
            firstKeyDictionary.Remove(firstKey);
    
            return true;
        }
    }
    

    Test the code...

        static void Main(string[] args)
        {
            var dict = new MultiKeyDictionary<string, long, long>();
            dict.Add("abc", 111, 1234);
            dict.Add("def", 222, 7890);
            dict.Add("hij", 333, 9090);
    
            Console.WriteLine(dict["abc"]); // expect 1234
            Console.WriteLine(dict["def"]); // expect 7890
            Console.WriteLine(dict[333]); // expect 9090
    
            Console.WriteLine();
            Console.WriteLine("removing def");
            dict.Remove("def");
    
            Console.WriteLine();
            Console.WriteLine("now we have:");
            foreach (var d in dict.GetKeyValuePairsOfFirstKey())
            {
                Console.WriteLine($"{d.Key} : {d.Value}");
            }
    
            Console.WriteLine();
            Console.WriteLine("removing 333");
            dict.Remove(333);
    
            Console.WriteLine();
            Console.WriteLine("now we have:");
            foreach (var d in dict.GetKeyValuePairsOfSecondKey())
            {
                Console.WriteLine($"{d.Key} : {d.Value}");
            }
    
            Console.ReadLine();
        }
    
    0 讨论(0)
  • 2020-12-14 00:27

    A very crude way that may suffice until I find a better one.

    class MyClass
    {
      public string StringKey = "";
      public int IntKey = 0;
    
      public override Equals(object obj)
      {
        // Code to return true if all fields are equal
      }
    }
    
    Dictionary <MyClass, string> MyDict;
    MyClass myClass;
    
    MyDict[MyDict.Keys.FirstOrDefault(x => x.Equals(MyClass))];
    

    For my money, the answer saying to use tuples is the right one. Unfortunately, my NuGet is too old to get the ValueTuple package I'd want to use so my fields aren't 'item1', 'item2' etc. That would be more confusing than what I've done here. When I change VS/NuGet versions, it's ValueTuples all the way for this kind of situation. Second time this week I've encountered the need!

    0 讨论(0)
  • 2020-12-14 00:31

    You can't do it just with a single Dictionary without losing look up speed. The reason is that if you were to create a composite key there is no meaningful value you can return when you override GetHashCode. This means an equality comparison would need to be done against every key until a dictionary entry is found. You would also have a potential problem with a composite key in this case: because your Equals method would check whether one property or the other are equal, the following keys would essentially be duplicate keys { Id=1, Name="Bob" } { Id=1, Name="Anna" }, which doesn't give me a warm fuzzy feeling.

    This leaves you with wrapping a dictionary, or pair of dictionaries with your own class.

    0 讨论(0)
  • 2020-12-14 00:32

    Maybe, something like this:

    public class TwoKeyDictionary<Tkey1, Tkey2, TValue>
    {
        private object m_data_lock = new object();
        private Dictionary<Tkey1, Tkey2> m_dic1 = new Dictionary<Tkey1, Tkey2>();
        private Dictionary<Tkey2, TValue> m_dic2 = new Dictionary<Tkey2, TValue>();
    
        public void AddValue(Tkey1 key1, Tkey2 key2, TValue value)
        {
            lock(m_data_lock)
            {
                m_dic1[key1] = key2;
                m_dic2[key2] = value;
            }
        }
    
        public TValue getByKey1(Tkey1 key1)
        {
            lock(m_data_lock)
                return m_dic2[m_dic1[key1]];
        }
    
        public TValue getByKey2(Tkey key2)
        {
            lock(m_data_lock)
                return m_dic2[key2];
        }
    
        public void removeByKey1(Tkey1 key1)
        {
            lock(m_data_lock)
            {
                Tkey2 tmp_key2 =   m_dic1[key1];
                m_dic1.Remove(key1);
                m_dic2.Remove(tmp_key2);
            }
        }
    
        public void removeByKey2(Tkey2 key2)
        {
            lock(m_data_lock)
            {
                Tkey1 tmp_key1 = m_dic1.First((kvp) => kvp.Value.Equals(key2)).Key;
                m_dic1.Remove(tmp_key1);
                m_dic2.Remove(key2);
            }
        }
    }
    

    I can offer a second solution, but it seems more slow and ugly vs. the first.

    public class TwoKeysDictionary<K1, K2, V>
    {
        private class TwoKeysValue<K1, K2, V>
        {
            public K1 Key1 { get; set; }
            public K2 Key2 { get; set; }
            public V Value { get; set; }
        }
    
        private List<TwoKeysValue<K1, K2, V>> m_list = new List<TwoKeysValue<K1, K2, V>>();
    
        public void Add(K1 key1, K2 key2, V value)
        {
            lock (m_list)
                m_list.Add(new TwoKeysValue<K1, K2, V>() { Key1 = key1, Key2 = key2, Value = value });
        }
    
        public V getByKey1(K1 key1)
        {
            lock (m_list)
                return m_list.First((tkv) => tkv.Key1.Equals(key1)).Value;
        }
    
        public V getByKey2(K2 key2)
        {
            lock (m_list)
                return m_list.First((tkv) => tkv.Key2.Equals(key2)).Value;
        }
    
        public void removeByKey1(K1 key1)
        {
            lock (m_list)
                m_list.Remove(m_list.First((tkv) => tkv.Key1.Equals(key1)));
        }
    
        public void removeByKey2(K2 key2)
        {
            lock (m_list)
                m_list.Remove(m_list.First((tkv) => tkv.Key2.Equals(key2)));
        }
    }
    

    In very bad case, when Keys are a big structures (i.e. big value-types) and Keys are equals by size, and values are small value-types (for instance, a byte), with first solution you had: one set of Key1 , two sets of Key2, one set of values = 3 sets of big objects and 1 set of small values. With second solution you had: one set of Key1 , one set of Key2, one set of values = 2 sets of big objects and small set with values. I.e. with using of first solution you need by 50% (or by lower) more memory space vs. second, but a second solution is a very, very slow vs. first.

    0 讨论(0)
  • 2020-12-14 00:37

    interesting question, here's one solution. You have to add an indexer for every key type you want to support though.

    public class NewDic<T>
    {
        public void Add(string key1, long key2, T value)
        {
            mDic.Add(key1, value);
            mDic.Add(key2, value);
        }
    
        public T this[string s]
        {
            get { return mDic[s]; }
        }
    
        public T this[long l]
        {
            get { return mDic[l]; }
        }
    
    
        Dictionary<object, T> mDic = new Dictionary<object, T>();
    }
    
            NewDic<long> dic = new NewDic<long>();
    
            dic.Add("abc", 20, 10);
    
            Console.WriteLine(dic["abc"]);
            Console.WriteLine(dic[20]);
    
    0 讨论(0)
提交回复
热议问题