在c#中合并2个或更多字典(Dictionary<TKey, TValue>)的最佳方法是什么? (像LINQ这样的3.0特性就可以了)。

我正在考虑一个方法签名,如下所示:

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries);

or

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries);

关于重复键的处理:在发生冲突的情况下,保存到字典中的值并不重要,只要它是一致的。


当前回答

与之前没有LINQ的情况下再次简化,如果存在则使用bool默认值非破坏性合并,如果为true则完全覆盖,而不是使用enum。它仍然适合我自己的需要,而不需要任何花哨的代码:

using System.Collections.Generic;
using System.Linq;

public static partial class Extensions
{
    public static void Merge<K, V>(this IDictionary<K, V> target, 
                                   IDictionary<K, V> source, 
                                   bool overwrite = false)
    {
        foreach (KeyValuePair _ in source)
            if (overwrite || !target.ContainsKey(_.Key))
                target[_.Key] = _.Value;
    }
}

其他回答

其平凡解为:

using System.Collections.Generic;
...
public static Dictionary<TKey, TValue>
    Merge<TKey,TValue>(IEnumerable<Dictionary<TKey, TValue>> dictionaries)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var dict in dictionaries)
        foreach (var x in dict)
            result[x.Key] = x.Value;
    return result;
}
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);

考虑到字典键查找和删除的性能,因为它们是哈希操作,并且考虑到问题的措辞是最好的方式,我认为下面是一个完全有效的方法,而其他方法有点过于复杂,恕我冒昧。

    public static void MergeOverwrite<T1, T2>(this IDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null) return;

        foreach (var e in newElements)
        {
            dictionary.Remove(e.Key); //or if you don't want to overwrite do (if !.Contains()
            dictionary.Add(e);
        }
    }

或者如果你在多线程应用程序中工作,你的字典无论如何都需要线程安全,你应该这样做:

    public static void MergeOverwrite<T1, T2>(this ConcurrentDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null || newElements.Count == 0) return;

        foreach (var ne in newElements)
        {
            dictionary.AddOrUpdate(ne.Key, ne.Value, (key, value) => value);
        }
    }

然后可以对其进行包装,使其处理字典的枚举。无论如何,您看到的是~O(3n)(所有条件都是完美的),因为. add()将在幕后执行额外的、不必要的但实际上是免费的Contains()。我觉得没有比这更好的了。

如果希望限制大型集合上的额外操作,则应该将将要合并的每个字典的Count相加,并将目标字典的容量设置为该值,这样可以避免以后调整大小的成本。最终产品是这样的…

    public static IDictionary<T1, T2> MergeAllOverwrite<T1, T2>(IList<IDictionary<T1, T2>> allDictionaries)
    {
        var initSize = allDictionaries.Sum(d => d.Count);
        var resultDictionary = new Dictionary<T1, T2>(initSize);
        allDictionaries.ForEach(resultDictionary.MergeOverwrite);
        return resultDictionary;
    }

注意,我在这个方法中引入了一个IList<T>…主要是因为如果你接受一个IEnumerable<T>,你已经向同一个集合的多个枚举开放了,如果你从一个延迟的LINQ语句中获得字典集合,这可能是非常昂贵的。

这是我的解决方案:它的行为类似于python中的dict.update()方法。

public static class DictionaryExtensions
{
    public static void Update<K,V>(this IDictionary<K, V> me, IDictionary<K, V> other)
    {
        foreach (var x in other)
        {
            me[x.Key] = x.Value;
        }
    }
}

对于c#新手来说,我害怕看到复杂的答案。

这里有一些简单的答案。 合并d1 d2,等等。字典和处理任何重叠键(“b”在下面的例子中):

示例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
}

示例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
}

对于更复杂的场景,请参见其他答案。 希望这有帮助。