Merging dictionaries in C#

前端 未结 26 1044
温柔的废话
温柔的废话 2020-11-22 08:25

What\'s the best way to merge 2 or more dictionaries (Dictionary) in C#? (3.0 features like LINQ are fine).

I\'m thinking of a method signa

相关标签:
26条回答
  • 2020-11-22 09:03

    @Tim: Should be a comment, but comments don't allow for code editing.

    Dictionary<string, string> t1 = new Dictionary<string, string>();
    t1.Add("a", "aaa");
    Dictionary<string, string> t2 = new Dictionary<string, string>();
    t2.Add("b", "bee");
    Dictionary<string, string> t3 = new Dictionary<string, string>();
    t3.Add("c", "cee");
    t3.Add("d", "dee");
    t3.Add("b", "bee");
    Dictionary<string, string> merged = t1.MergeLeft(t2, t2, t3);
    

    Note: I applied the modification by @ANeves to the solution by @Andrew Orsich, so the MergeLeft looks like this now:

    public static Dictionary<K, V> MergeLeft<K, V>(this Dictionary<K, V> me, params IDictionary<K, V>[] others)
        {
            var newMap = new Dictionary<K, V>(me, me.Comparer);
            foreach (IDictionary<K, V> src in
                (new List<IDictionary<K, V>> { me }).Concat(others))
            {
                // ^-- echk. Not quite there type-system.
                foreach (KeyValuePair<K, V> p in src)
                {
                    newMap[p.Key] = p.Value;
                }
            }
            return newMap;
        }
    
    0 讨论(0)
  • 2020-11-22 09:03
    using System.Collections.Generic;
    using System.Linq;
    
    public static class DictionaryExtensions
    {
        public enum MergeKind { SkipDuplicates, OverwriteDuplicates }
        public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, MergeKind kind = MergeKind.SkipDuplicates) =>
            source.ToList().ForEach(_ => { if (kind == MergeKind.OverwriteDuplicates || !target.ContainsKey(_.Key)) target[_.Key] = _.Value; });
    }
    

    You can either skip/ignore (default) or overwrite the duplicates: And Bob's your uncle provided you are not overly fussy about Linq performance but prefer instead concise maintainable code as I do: in which case you can remove the default MergeKind.SkipDuplicates to enforce a choice for the caller and make the developer cognisant of what the results will be!

    0 讨论(0)
  • 2020-11-22 09:05

    This partly depends on what you want to happen if you run into duplicates. For instance, you could do:

    var result = dictionaries.SelectMany(dict => dict)
                             .ToDictionary(pair => pair.Key, pair => pair.Value);
    

    That will throw an exception if you get any duplicate keys.

    EDIT: If you use ToLookup then you'll get a lookup which can have multiple values per key. You could then convert that to a dictionary:

    var result = dictionaries.SelectMany(dict => dict)
                             .ToLookup(pair => pair.Key, pair => pair.Value)
                             .ToDictionary(group => group.Key, group => group.First());
    

    It's a bit ugly - and inefficient - but it's the quickest way to do it in terms of code. (I haven't tested it, admittedly.)

    You could write your own ToDictionary2 extension method of course (with a better name, but I don't have time to think of one now) - it's not terribly hard to do, just overwriting (or ignoring) duplicate keys. The important bit (to my mind) is using SelectMany, and realising that a dictionary supports iteration over its key/value pairs.

    0 讨论(0)
  • 2020-11-22 09:05

    The following works for me. If there are duplicates, it will use dictA's value.

    public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> dictA, IDictionary<TKey, TValue> dictB)
        where TValue : class
    {
        return dictA.Keys.Union(dictB.Keys).ToDictionary(k => k, k => dictA.ContainsKey(k) ? dictA[k] : dictB[k]);
    }
    
    0 讨论(0)
  • 2020-11-22 09:06

    This doesn't explode if there are multiple keys ("righter" keys replace "lefter" keys), can merge a number of dictionaries (if desired) and preserves the type (with the restriction that it requires a meaningful default public constructor):

    public static class DictionaryExtensions
    {
        // Works in C#3/VS2008:
        // Returns a new dictionary of this ... others merged leftward.
        // Keeps the type of 'this', which must be default-instantiable.
        // Example: 
        //   result = map.MergeLeft(other1, other2, ...)
        public static T MergeLeft<T,K,V>(this T me, params IDictionary<K,V>[] others)
            where T : IDictionary<K,V>, new()
        {
            T newMap = new T();
            foreach (IDictionary<K,V> src in
                (new List<IDictionary<K,V>> { me }).Concat(others)) {
                // ^-- echk. Not quite there type-system.
                foreach (KeyValuePair<K,V> p in src) {
                    newMap[p.Key] = p.Value;
                }
            }
            return newMap;
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 09:10

    Got scared to see complex answers, being new to C#.

    Here are some simple answers.
    Merging d1, d2, and so on.. dictionaries and handle any overlapping keys ("b" in below examples):

    Example 1

    {
        // 2 dictionaries,  "b" key is common with different values
    
        var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
        var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };
    
        var result1 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
        // result1 is  a=10, b=21, c=30    That is, took the "b" value of the first dictionary
    
        var result2 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
        // result2 is  a=10, b=22, c=30    That is, took the "b" value of the last dictionary
    }
    

    Example 2

    {
        // 3 dictionaries,  "b" key is common with different values
    
        var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
        var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };
        var d3 = new Dictionary<string, int>() { { "d", 40 }, { "b", 23 } };
    
        var result1 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
        // result1 is  a=10, b=21, c=30, d=40    That is, took the "b" value of the first dictionary
    
        var result2 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
        // result2 is  a=10, b=23, c=30, d=40    That is, took the "b" value of the last dictionary
    }
    

    For more complex scenarios, see other answers.
    Hope that helped.

    0 讨论(0)
提交回复
热议问题