Merging dictionaries in C#
在C语言中合并2个或更多字典(
我正在考虑一个方法签名,它的行如下:
1 2 | public static Dictionary<TKey,TValue> Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries); |
或
1 2 | public static Dictionary<TKey,TValue> Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries); |
编辑:从Jaredpar和JonSkeet那里得到了一个很酷的解决方案,但是我正在考虑处理重复的键。在发生冲突的情况下,保存到dict中的值并不重要,只要它是一致的。
这在一定程度上取决于如果遇到重复项,您希望发生什么。例如,您可以执行以下操作:
1 2 | var result = dictionaries.SelectMany(dict => dict) .ToDictionary(pair => pair.Key, pair => pair.Value); |
如果你有重复的钥匙,它会爆炸的。
编辑:如果您使用tolookup,那么您将得到一个查找,每个键可以有多个值。然后您可以将其转换为字典:
1 2 3 | var result = dictionaries.SelectMany(dict => dict) .ToLookup(pair => pair.Key, pair => pair.Value) .ToDictionary(group => group.Key, group => group.First()); |
这有点难看——而且效率也很低——但就代码而言,这是最快的方法。(当然,我还没有测试过。)
当然,您可以编写自己的ToDictionary2扩展方法(使用更好的名称,但我现在没有时间考虑一个扩展方法),这并不难,只需覆盖(或忽略)重复的键即可。重要的一点(在我看来)是使用selectmany,并且意识到字典支持其键/值对的迭代。
我会这样做:
1 | dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value)); |
简单而简单。根据这篇博文,它甚至比大多数循环更快,因为它的底层实现是通过索引而不是枚举器访问元素的(见这个答案)。
当然,如果存在重复项,它会抛出一个异常,因此在合并之前必须进行检查。
好吧,我参加聚会迟到了,但我用的是这个。如果有多个键("righter"键替换"lefter"键),它不会爆炸,可以合并多个字典(如果需要)并保留类型(限制条件是它需要有意义的默认公共构造函数):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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; } } |
简单的解决方案是:
1 2 3 4 5 6 7 8 9 10 11 | 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; } |
尝试以下操作
1 2 3 4 5 | 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); } |
1 2 | Dictionary<String, String> allTables = new Dictionary<String, String>(); allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value); |
以下是我的作品。如果有重复项,它将使用dicta的值。
1 2 3 4 5 | 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]); } |
我很晚才去参加聚会,可能错过了一些东西,但是如果没有重复的键,或者,正如OP所说,"万一发生碰撞,只要它一致,保存到dict的值就无关紧要,"这个(将d2合并到d1)有什么问题?
1 2 3 4 | foreach (KeyValuePair<string,int> item in D2) { D1[item.Key] = item.Value; } |
这看起来很简单,也许太简单了,我想知道我是否错过了什么。这就是我在某些代码中使用的代码,我知道没有重复的密钥。不过,我仍在测试中,所以我现在想知道我是否忽略了一些东西,而不是稍后再发现。
下面是我使用的助手函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); } } } |
增加一个
另外,为了获得最大的灵活性,您应该将它们输入为
1 2 3 4 5 6 7 8 9 | 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); } |
考虑到字典键查找和删除的性能,因为它们是散列操作,并且考虑到问题的措词是最好的方法,我认为下面是一个完全有效的方法,其他的方法有点过于复杂,imho。
1 2 3 4 5 6 7 8 9 10 | 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); } } |
或者,如果您正在一个多线程应用程序中工作,并且您的字典无论如何都需要是线程安全的,那么您应该这样做:
1 2 3 4 5 6 7 8 9 | 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)(所有条件都是完美的),因为
如果要限制对大型集合的额外操作,则应将要合并的每个字典的
1 2 3 4 5 6 7 | 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; } |
注意,我用了一个
该党现在几乎已经死了,但这里有一个"改进"的用户166390版本,它进入了我的扩展库。除了一些细节外,我还添加了一个委托来计算合并值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | /// <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; } |
基于上述答案,但添加func参数以让调用方处理重复项:
1 2 3 4 5 6 7 8 9 10 | 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)); } |
@蒂姆:应该是注释,但是注释不允许代码编辑。
1 2 3 4 5 6 7 8 9 | 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的解决方案中,因此mergeleft现在看起来是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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,你可以用这样的一行来回答它。
1 2 3 | Dictionary<T1,T2> merged; Dictionary<T1,T2> mergee; mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value)); |
或
1 | mergee.ToList().ForEach(kvp => merged.Append(kvp)); |
害怕看到复杂的答案,对C不熟悉。
下面是一些简单的答案。合并d1、d2等。字典和处理任何重叠键(以下示例中为"b"):
例1
1 2 3 4 5 6 7 8 9 10 11 12 | { // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 | { // 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 } |
有关更复杂的场景,请参阅其他答案。希望有帮助。
使用扩展方法合并。当有重复的键时,它不会抛出异常,而是用第二个字典中的键替换这些键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; } } |
用途:
1 | Dictionary<string, string> merged = first.Merge(second); |
1 2 3 4 5 6 7 8 9 | 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性能,而是像我一样喜欢简洁的可维护代码,那么Bob的叔叔也可以这样做:在这种情况下,您可以删除默认的mergekind.skip duplicates以强制调用方选择并使开发人员了解结果。TS将是!
1 2 3 4 5 6 7 8 9 10 11 | 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; } |
或:
1 2 3 4 5 6 7 | 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"获胜。
合并时使用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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(); } } |
用途:
1 2 3 4 | // 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); |