在C#中合并词典

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

我正在考虑一个方法签名:

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

编辑:从Jare​​dPar和乔恩Skeet,得到了一个很酷的解决scheme,但我想的东西,处理重复键。 在发生碰撞的情况下,只要保持一致,哪个值保存到字典中并不重要。

这部分取决于你想要发生什么,如果你遇到重复。 例如,你可以这样做:

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

这有点丑陋 – 效率低下,但是它是以代码的方式来做的最快的方法。 (诚​​然,我还没有testing过。)

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

我会这样做:

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

简单和容易。 根据这个博客文章,它比大多数循环更快,因为它的底层实现通过索引而不是枚举器访问元素(请参阅此答案) 。

如果有重复的话,它当然会抛出一个exception,所以你必须在合并之前进行检查。

呃,我迟到了,但是这是我用的。 如果有多个键(“righter”键代替“lefter”键),它可以合并一些词典(如果需要)并保留types(限制它需要一个有意义的默认公共构造函数)。

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

微不足道的解决办法是:

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

尝试以下

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

以下为我工作。 如果有重复,它将使用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]); } 

这是我使用的一个辅助函数:

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

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

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

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

  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 ,并将目标字典的容量设置为该字典的容量,这样可以避免以后resize的成本。 所以,最终产品是这样的…

  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语句中收集字典。

如何添加一个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); } 

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

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

我知道这是一个古老的问题,但是因为我们现在有了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)); 

我对派对很迟,也许错过了一些东西,但是如果没有任何重复的键,或者像OP所说的那样,“如果发生碰撞,只要是在字典中保存了哪个值,并不重要一致的“,这个(D2合并到D1)有什么问题?

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

这似乎很简单,也许太简单,我不知道我是否失去了一些东西。 这是我在一些代码中使用的,我知道没有重复的键。 尽pipe如此,我还在testing中,所以现在我很想知道我是否忽略了某些事情,而不是稍后再发现。

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

注意:@Andrew Orsich将@ANeves的修改应用于解决scheme,所以现在MergeLeft看起来像这样:

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

使用扩展方法合并。 当有重复键时,它不会抛出exception,而是用第二个字典中的键replace这些键。

 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合并,将比较项目与不同的值/types进行比较。 在这里,我们将从KeyValuePair (枚举字典时的项目types)映射到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); 

要么 :

 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”获胜的联盟。