在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);

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


当前回答

试试

namespace Extensions
{
    public static class DictionaryExtensions
    {
        public static Dictionary<T, Y> MergeWith<T, Y>(this Dictionary<T, Y> dictA,
            Dictionary<T, Y> dictB)
        {
            foreach (var item in dictB)
            {
                if (dictA.ContainsKey(item.Key))
                    dictA[item.Key] = item.Value;
                else
                    dictA.Add(item.Key, item.Value);
            }
            return dictA;
        }
    }
}

当你想合并两个字典时

    var d1 = new Dictionary<string, string>();
    var d2 = new Dictionary<string, string>();
    d1.MergeWith(d2);

其他回答

@Tim:应该是注释,但是注释不允许代码编辑。

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);

注意:我应用了@ANeves对@Andrew Orsich的解决方案的修改,所以mergleft现在看起来像这样:

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;
    }

基于上面的答案,但添加一个Func-parameter,让调用者处理重复:

public static Dictionary<TKey, TValue> Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> dicts, 
                                                           Func<IGrouping<TKey, TValue>, TValue> resolveDuplicates)
{
    if (resolveDuplicates == null)
        resolveDuplicates = new Func<IGrouping<TKey, TValue>, TValue>(group => group.First());

    return dicts.SelectMany<Dictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(dict => dict)
                .ToLookup(pair => pair.Key, pair => pair.Value)
                .ToDictionary(group => group.Key, group => resolveDuplicates(group));
}

如何添加一个参数重载?

此外,您应该将它们输入为dictionary以获得最大的灵活性。

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(IEnumerable<IDictionary<TKey, TValue>> dictionaries)
{
    // ...
}

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(params IDictionary<TKey, TValue>[] dictionaries)
{
    return Merge((IEnumerable<TKey, TValue>) dictionaries);
}

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

    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语句中获得字典集合,这可能是非常昂贵的。

选项1:这取决于在确定两个字典中都没有重复键的情况下想要发生什么。比你能做的:

var result = dictionary1.Union(dictionary2).ToDictionary(k => k.Key, v => v.Value)

注意:如果在字典中获得任何重复的键,将抛出错误。

选项2:如果你可以有重复的键,那么你必须使用where子句来处理重复的键。

var result = dictionary1.Union(dictionary2.Where(k => !dictionary1.ContainsKey(k.Key))).ToDictionary(k => k.Key, v => v.Value)

注意:它不会得到重复的密钥。如果有任何重复的键,那么它将获得dictionary1的键。

选项3:如果你想使用ToLookup。然后您将得到一个查找,每个键可以有多个值。你可以把这个查找转换成一个字典:

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