在c#中合并字典

在c#中合并2个或更多字典(Dictionary<T1,T2>)的最佳方法是什么? (像LINQ这样的3.0特性是很好的)

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

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

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

从JaredPar和Jon Skeet那里得到了一个很酷的解决方案,但我在考虑处理重复键的东西。在发生冲突的情况下,保存到字典中的值并不重要,只要它是一致的。

389646 次浏览

其平凡解为:

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

试试下面的方法

static Dictionary<TKey, TValue>
Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> enumerable)
{
return enumerable.SelectMany(x => x).ToDictionary(x => x.Key, y => y.Value);
}

这在一定程度上取决于如果遇到重复项,你希望发生什么。例如,你可以这样做:

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

如果您获得任何重复的键,将抛出异常。

编辑:如果你使用ToLookup,那么你会得到一个查找,每个键可以有多个值。你可以然后把它转换成一个字典:

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

这有点难看——而且效率很低——但从代码的角度来说,这是最快的方法。(不得不承认,我还没有测试过它。)

当然,您也可以编写自己的ToDictionary2扩展方法(有一个更好的名字,但我现在没有时间去想)——这并不难做到,只是覆盖(或忽略)重复的键。重要的一点(在我看来)是使用SelectMany,并意识到字典支持键/值对的迭代。

添加params重载怎么样?

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

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

下面是我使用的一个helper函数:

using System.Collections.Generic;
namespace HelperMethods
{
public static class MergeDictionaries
{
public static void Merge<TKey, TValue>(this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
{
if (second == null || first == null) return;
foreach (var item in second)
if (!first.ContainsKey(item.Key))
first.Add(item.Key, item.Value);
}
}
}
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);

如果有多个键(“右”键取代“左”键),这不会爆炸,可以合并一些字典(如果需要),并保留类型(限制它需要一个有意义的默认公共构造函数):

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


}

我会这样做:

dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));

简单易行。根据这篇博文,它甚至比大多数循环都快,因为它的底层实现通过索引而不是通过枚举数(请看这个答案)访问元素。

如果存在重复,它当然会抛出异常,因此您必须在合并之前进行检查。

基于上面的答案,但添加一个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));
}
派对现在几乎已经死了,但是这里有一个user166390的“改进”版本,它已经进入了我的扩展库。 除了一些细节之外,我还添加了一个委托来计算合并的值
/// <summary>
/// Merges a dictionary against an array of other dictionaries.
/// </summary>
/// <typeparam name="TResult">The type of the resulting dictionary.</typeparam>
/// <typeparam name="TKey">The type of the key in the resulting dictionary.</typeparam>
/// <typeparam name="TValue">The type of the value in the resulting dictionary.</typeparam>
/// <param name="source">The source dictionary.</param>
/// <param name="mergeBehavior">A delegate returning the merged value. (Parameters in order: The current key, The current value, The previous value)</param>
/// <param name="mergers">Dictionaries to merge against.</param>
/// <returns>The merged dictionary.</returns>
public static TResult MergeLeft<TResult, TKey, TValue>(
this TResult source,
Func<TKey, TValue, TValue, TValue> mergeBehavior,
params IDictionary<TKey, TValue>[] mergers)
where TResult : IDictionary<TKey, TValue>, new()
{
var result = new TResult();
var sources = new List<IDictionary<TKey, TValue>> { source }
.Concat(mergers);


foreach (var kv in sources.SelectMany(src => src))
{
TValue previousValue;
result.TryGetValue(kv.Key, out previousValue);
result[kv.Key] = mergeBehavior(kv.Key, kv.Value, previousValue);
}


return result;
}

使用扩展方法合并。当存在重复的键时,它不会抛出异常,而是用第二个字典中的键替换这些键。

internal static class DictionaryExtensions
{
public static Dictionary<T1, T2> Merge<T1, T2>(this Dictionary<T1, T2> first, Dictionary<T1, T2> second)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");


var merged = new Dictionary<T1, T2>();
first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
second.ToList().ForEach(kv => merged[kv.Key] = kv.Value);


return merged;
}
}

用法:

Dictionary<string, string> merged = first.Merge(second);

使用EqualityComparer进行合并,将项目映射到不同的值/类型进行比较。这里我们将从KeyValuePair(枚举字典时的项类型)映射到Key

public class MappedEqualityComparer<T,U> : EqualityComparer<T>
{
Func<T,U> _map;


public MappedEqualityComparer(Func<T,U> map)
{
_map = map;
}


public override bool Equals(T x, T y)
{
return EqualityComparer<U>.Default.Equals(_map(x), _map(y));
}


public override int GetHashCode(T obj)
{
return _map(obj).GetHashCode();
}
}

用法:

// if dictA and dictB are of type Dictionary<int,string>
var dict = dictA.Concat(dictB)
.Distinct(new MappedEqualityComparer<KeyValuePair<int,string>,int>(item => item.Key))
.ToDictionary(item => item.Key, item=> item.Value);

我来的很晚,可能遗漏了一些东西,但如果没有重复的键,或者如OP所说,“如果发生碰撞,保存到字典中的值并不重要,只要它是一致的”;这个怎么了(D2和D1合并)?

foreach (KeyValuePair<string,int> item in D2)
{
D1[item.Key] = item.Value;
}

这看起来很简单,也许太简单了,我想知道我是否遗漏了什么。这是我在一些代码中使用的,我知道没有重复的键。不过,我仍在测试中,所以我现在就想知道我是否忽略了一些东西,而不是以后才发现。

下面的方法对我有用。如果存在重复项,则使用dictA的值。

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

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

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

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

我知道这是一个老问题,但是因为我们现在有LINQ,你可以像这样在一行中完成它

Dictionary<T1,T2> merged;
Dictionary<T1,T2> mergee;
mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value));

mergee.ToList().ForEach(kvp => merged.Append(kvp));

或者:

public static IDictionary<TKey, TValue> Merge<TKey, TValue>( IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
{
return x
.Except(x.Join(y, z => z.Key, z => z.Key, (a, b) => a))
.Concat(y)
.ToDictionary(z => z.Key, z => z.Value);
}

结果是一个联合,对于重复的条目,“y”胜出。

对于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
}

更复杂的场景请参见其他答案

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

你可以跳过/忽略(默认)或覆盖副本:如果你对Linq性能不过分挑剔,而是像我一样喜欢简洁的可维护代码:在这种情况下,你可以删除默认的MergeKind。skipduplicate用于强制调用者进行选择,并使开发人员知道结果将是什么!

public static IDictionary<K, V> AddRange<K, V>(this IDictionary<K, V> one, IDictionary<K, V> two)
{
foreach (var kvp in two)
{
if (one.ContainsKey(kvp.Key))
one[kvp.Key] = two[kvp.Key];
else
one.Add(kvp.Key, kvp.Value);
}
return one;
}

这取决于如果你确定两个字典中都没有重复的键,你想要发生什么。比你能做的:

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

如果字典中有重复的键,将抛出错误。

如果你可以有重复的键,那么你必须使用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());

@user166390的回答版本增加了IEqualityComparer参数,以允许不区分大小写的键比较。

    public static T MergeLeft<T, K, V>(this T me, params Dictionary<K, V>[] others)
where T : Dictionary<K, V>, new()
{
return me.MergeLeft(me.Comparer, others);
}


public static T MergeLeft<T, K, V>(this T me, IEqualityComparer<K> comparer, params Dictionary<K, V>[] others)
where T : Dictionary<K, V>, new()
{
T newMap = Activator.CreateInstance(typeof(T), new object[] { comparer }) as T;


foreach (Dictionary<K, V> src in
(new List<Dictionary<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;
}

与之前没有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;
}
}

注意,如果你使用一个名为“Add”的扩展方法,你可以使用集合初始化器来组合尽可能多的字典,就像这样:

public static void Add<K, V>(this Dictionary<K, V> d, Dictionary<K, V> other) {
foreach (var kvp in other)
{
if (!d.ContainsKey(kvp.Key))
{
d.Add(kvp.Key, kvp.Value);
}
}
}




var s0 = new Dictionary<string, string> {
{ "A", "X"}
};
var s1 = new Dictionary<string, string> {
{ "A", "X" },
{ "B", "Y" }
};
// Combine as many dictionaries and key pairs as needed
var a = new Dictionary<string, string> {
s0, s1, s0, s1, s1, { "C", "Z" }
};

我将@orip的简单和非垃圾的创建解决方案进行拆分,以提供一个适当的AddAll()Merge(),以处理将一个字典添加到另一个字典的简单情况。

using System.Collections.Generic;
...
public static Dictionary<TKey, TValue>
AddAll<TKey,TValue>(Dictionary<TKey, TValue> dest, Dictionary<TKey, TValue> source)
{
foreach (var x in source)
dest[x.Key] = x.Value;
}


public static Dictionary<TKey, TValue>
Merge<TKey,TValue>(IEnumerable<Dictionary<TKey, TValue>> dictionaries)
{
var result = new Dictionary<TKey, TValue>();
foreach (var dict in dictionaries)
result.AddAll(dict);
return result;
}

根据这篇文章中所有的答案,这里是我能想到的最通用的解决方案。

我创建了两个版本的IDictionary.Merge()扩展:

  • 合并<T, U>(source celeft, source ceright)
  • (source celeft, source ceright, Func<U, U, U>mergeExpression)

其中第二个是第一个的修改版本,允许你指定一个lambda表达式来处理像这样的重复:

Dictionary<string, object> customAttributes =
HtmlHelper
.AnonymousObjectToHtmlAttributes(htmlAttributes)
.ToDictionary(
ca => ca.Key,
ca => ca.Value
);


Dictionary<string, object> fixedAttributes =
new RouteValueDictionary(
new {
@class = "form-control"
}).ToDictionary(
fa => fa.Key,
fa => fa.Value
);


//appending the html class attributes
IDictionary<string, object> editorAttributes = fixedAttributes.Merge(customAttributes, (leftValue, rightValue) => leftValue + " " + rightValue);

(你可以专注于ToDictionary()Merge()部分)

下面是扩展类(右边有2个版本的扩展,接受IDictionary的集合):

  public static class IDictionaryExtension
{
public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IDictionary<T, U> sourceRight)
{
IDictionary<T, U> result = new Dictionary<T,U>();


sourceLeft
.Concat(sourceRight)
.ToList()
.ForEach(kvp =>
result[kvp.Key] = kvp.Value
);


return result;
}


public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IDictionary<T, U> sourceRight, Func<U, U, U> mergeExpression)
{
IDictionary<T, U> result = new Dictionary<T,U>();


//Merge expression example
//(leftValue, rightValue) => leftValue + " " + rightValue;


sourceLeft
.Concat(sourceRight)
.ToList()
.ForEach(kvp =>
result[kvp.Key] =
(!result.ContainsKey(kvp.Key))
? kvp.Value
: mergeExpression(result[kvp.Key], kvp.Value)
);


return result;
}




public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IEnumerable<IDictionary<T, U>> sourcesRight)
{
IDictionary<T, U> result = new Dictionary<T, U>();
      

new[] { sourceLeft }
.Concat(sourcesRight)
.ToList()
.ForEach(dic =>
result = result.Merge(dic)
);


return result;
}


public static IDictionary<T, U> Merge<T, U>(this IDictionary<T, U> sourceLeft, IEnumerable<IDictionary<T, U>> sourcesRight, Func<U, U, U> mergeExpression)
{
IDictionary<T, U> result = new Dictionary<T, U>();


new[] { sourceLeft }
.Concat(sourcesRight)
.ToList()
.ForEach(dic =>
result = result.Merge(dic, mergeExpression)
);


return result;
}
}

mergeExpression让您轻松地处理您想合并项目的方式,如加法,除法,乘法或任何您想要的特定过程。

请注意,我还没有测试扩展的集合版本…它们可能仍然需要一些调整。

此外,扩展不修改原来的字典,你必须分配回来,如果你想。

这是我的解决方案:它的行为类似于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;
}
}
}

试试

namespace Extentions
{
public static class DictionayExtensions
{
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);