博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在C#中合并字典
阅读量:2290 次
发布时间:2019-05-09

本文共 14857 字,大约阅读时间需要 49 分钟。

在C#中合并两个或多个词典( <T1,T2> )的最佳方法是什么? (像LINQ这样的3.0功能很好)。

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

public static Dictionary
(Dictionary
[] dictionaries);

要么

public static Dictionary
Merge
(IEnumerable
> dictionaries);

编辑:从Jare​​dPar和Jon Skeet得到一个很酷的解决方案,但我正在考虑处理重复键的东西。 在发生碰撞的情况下,只要它是一致的,将哪个值保存到dict并不重要。


#1楼

这是我使用的辅助函数:

using System.Collections.Generic;namespace HelperMethods{    public static class MergeDictionaries    {        public static void Merge
(this IDictionary
first, IDictionary
second) { if (second == null || first == null) return; foreach (var item in second) if (!first.ContainsKey(item.Key)) first.Add(item.Key, item.Value); } }}

#2楼

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

public static Dictionary
Merge
(this IEnumerable
> dicts, Func
, TValue> resolveDuplicates){ if (resolveDuplicates == null) resolveDuplicates = new Func
, TValue>(group => group.First()); return dicts.SelectMany
, KeyValuePair
>(dict => dict) .ToLookup(pair => pair.Key, pair => pair.Value) .ToDictionary(group => group.Key, group => resolveDuplicates(group));}

#3楼

派对现在已经死了,但这里是用户166390的“改进版”,它进入了我的扩展库。 除了一些细节,我添加了一个委托来计算合并的值。

/// /// Merges a dictionary against an array of other dictionaries./// /// 
The type of the resulting dictionary.
///
The type of the key in the resulting dictionary.
///
The type of the value in the resulting dictionary.
/// The source dictionary./// A delegate returning the merged value. (Parameters in order: The current key, The current value, The previous value)/// Dictionaries to merge against.///
The merged dictionary.
public static TResult MergeLeft
( this TResult source, Func
mergeBehavior, params IDictionary
[] mergers) where TResult : IDictionary
, new(){ var result = new TResult(); var sources = new List
> { 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;}

#4楼

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

internal static class DictionaryExtensions{    public static Dictionary
Merge
(this Dictionary
first, Dictionary
second) { if (first == null) throw new ArgumentNullException("first"); if (second == null) throw new ArgumentNullException("second"); var merged = new Dictionary
(); first.ToList().ForEach(kv => merged[kv.Key] = kv.Value); second.ToList().ForEach(kv => merged[kv.Key] = kv.Value); return merged; }}

用法:

Dictionary
merged = first.Merge(second);

#5楼

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

public class MappedEqualityComparer
: EqualityComparer
{ Func
_map; public MappedEqualityComparer(Func
map) { _map = map; } public override bool Equals(T x, T y) { return EqualityComparer
.Default.Equals(_map(x), _map(y)); } public override int GetHashCode(T obj) { return _map(obj).GetHashCode(); }}

用法:

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

#6楼

我很晚才参加派对,也许会遗漏一些东西,但如果要么没有重复的密钥,或者正如OP所说的那样,“如果发生碰撞,只要它是d,哪个值保存到dict就没关系了。一致,“这个有什么问题(把D2合并到D1)?

foreach (KeyValuePair
item in D2) { D1[item.Key] = item.Value; }

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


#7楼

以下适用于我。 如果有重复项,它将使用dictA的值。

public static IDictionary
Merge
(this IDictionary
dictA, IDictionary
dictB) where TValue : class{ return dictA.Keys.Union(dictB.Keys).ToDictionary(k => k, k => dictA.ContainsKey(k) ? dictA[k] : dictB[k]);}

#8楼

Dictionary
allTables = new Dictionary
();allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);

#9楼

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

public static void MergeOverwrite
(this IDictionary
dictionary, IDictionary
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
(this ConcurrentDictionary
dictionary, IDictionary
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
MergeAllOverwrite
(IList
> allDictionaries) { var initSize = allDictionaries.Sum(d => d.Count); var resultDictionary = new Dictionary
(initSize); allDictionaries.ForEach(resultDictionary.MergeOverwrite); return resultDictionary; }

请注意,我接受了一个IList<T>到这个方法...主要是因为如果你接受一个IEnumerable<T> ,你已经打开了自己的同一组的多个枚举,如果你有这个可能是非常昂贵的您从延迟的LINQ语句中收集的词典。


#10楼

如果有多个键(“righter”键替换“lefter”键),则不会爆炸,可以合并多个词典(如果需要)并保留类型(限制它需要有意义的默认公共构造函数):

public static class DictionaryExtensions{    // Works in 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
(this T me, params IDictionary
[] others) where T : IDictionary
, new() { T newMap = new T(); foreach (IDictionary
src in (new List
> { me }).Concat(others)) { // ^-- echk. Not quite there type-system. foreach (KeyValuePair
p in src) { newMap[p.Key] = p.Value; } } return newMap; }}

#11楼

@Tim:应该是评论,但评论不允许进行代码编辑。

Dictionary
t1 = new Dictionary
();t1.Add("a", "aaa");Dictionary
t2 = new Dictionary
();t2.Add("b", "bee");Dictionary
t3 = new Dictionary
();t3.Add("c", "cee");t3.Add("d", "dee");t3.Add("b", "bee");Dictionary
merged = t1.MergeLeft(t2, t2, t3);

注意:我将@ANeves的修改应用于@Andrew Orsich的解决方案,因此MergeLeft现在看起来像这样:

public static Dictionary
MergeLeft
(this Dictionary
me, params IDictionary
[] others) { var newMap = new Dictionary
(me, me.Comparer); foreach (IDictionary
src in (new List
> { me }).Concat(others)) { // ^-- echk. Not quite there type-system. foreach (KeyValuePair
p in src) { newMap[p.Key] = p.Value; } } return newMap; }

#12楼

琐碎的解决方案是:

using System.Collections.Generic;...public static Dictionary
Merge
(IEnumerable
> dictionaries){ var result = new Dictionary
(); foreach (var dict in dictionaries) foreach (var x in dict) result[x.Key] = x.Value; return result;}

#13楼

请尝试以下方法

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

#14楼

这部分取决于你遇到重复的事情。 例如,你可以这样做:

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,并意识到字典支持迭代其键/值对。


#15楼

如何添加params重载?

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

public static IDictionary
Merge
(IEnumerable
> dictionaries){ // ...}public static IDictionary
Merge
(params IDictionary
[] dictionaries){ return Merge((IEnumerable
) dictionaries);}

#16楼

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

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

要么

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

#17楼

要么 :

public static IDictionary
Merge
( IDictionary
x, IDictionary
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”获胜。


#18楼

害怕看到复杂的答案,不熟悉C#。

这是一些简单的答案。

合并d1,d2等字典并处理任何重叠键(以下示例中的“b”):

例1

{    // 2 dictionaries,  "b" key is common with different values    var d1 = new Dictionary
() { { "a", 10 }, { "b", 21 } }; var d2 = new Dictionary
() { { "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
() { { "a", 10 }, { "b", 21 } }; var d2 = new Dictionary
() { { "c", 30 }, { "b", 22 } }; var d3 = new Dictionary
() { { "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}

有关更复杂的方案,请参阅其他答案。

希望有所帮助。


#19楼

using System.Collections.Generic;using System.Linq;public static class DictionaryExtensions{    public enum MergeKind { SkipDuplicates, OverwriteDuplicates }    public static void Merge
(this IDictionary
target, IDictionary
source, MergeKind kind = MergeKind.SkipDuplicates) => source.ToList().ForEach(_ => { if (kind == MergeKind.OverwriteDuplicates || !target.ContainsKey(_.Key)) target[_.Key] = _.Value; });}

您可以跳过/忽略(默认)或覆盖重复项:如果您对Linq性能不过于挑剔,那么Bob就是您的叔叔,但我更喜欢简洁的可维护代码:在这种情况下,您可以删除默认的MergeKind.SkipDuplicates来强制执行呼叫者的选择,让开发人员认识到结果将是什么!


#20楼

public static IDictionary
AddRange
(this IDictionary
one, IDictionary
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; }

#21楼

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

#22楼

来自@ user166390的版本使用添加的IEqualityComparer参数进行回答,以允许不区分大小写的密钥比较。

public static T MergeLeft
(this T me, params Dictionary
[] others) where T : Dictionary
, new() { return me.MergeLeft(me.Comparer, others); } public static T MergeLeft
(this T me, IEqualityComparer
comparer, params Dictionary
[] others) where T : Dictionary
, new() { T newMap = Activator.CreateInstance(typeof(T), new object[] { comparer }) as T; foreach (Dictionary
src in (new List
> { me }).Concat(others)) { // ^-- echk. Not quite there type-system. foreach (KeyValuePair
p in src) { newMap[p.Key] = p.Value; } } return newMap; }

#23楼

fromDic.ToList().ForEach(x =>        {            if (toDic.ContainsKey(x.Key))                toDic.Remove(x.Key);            toDic.Add(x);        });

#24楼

使用简化与我之前的回答相比,bool默认为非破坏性合并(如果存在)或完全覆盖(如果为true)而不是使用枚举。 它仍然适合我自己的需要,而不需要任何更高级的代码:

using System.Collections.Generic;using System.Linq;public static partial class Extensions{    public static void Merge
(this IDictionary
target, IDictionary
source, bool overwrite = false) { source.ToList().ForEach(_ => { if ((!target.ContainsKey(_.Key)) || overwrite) target[_.Key] = _.Value; }); }}

#25楼

请注意,如果您使用名为“Add”的扩展方法,则可以使用集合初始值设定项根据需要组合尽可能多的字典,如下所示:

public static void Add
(this Dictionary
d, Dictionary
other) { foreach (var kvp in other) { if (!d.ContainsKey(kvp.Key)) { d.Add(kvp.Key, kvp.Value); } }}var s0 = new Dictionary
{ { "A", "X"}};var s1 = new Dictionary
{ { "A", "X" }, { "B", "Y" }};// Combine as many dictionaries and key pairs as neededvar a = new Dictionary
{ s0, s1, s0, s1, s1, { "C", "Z" }};

#26楼

我会这样做:

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

简单易行。 根据它比大多数循环更快,因为它的底层实现通过索引而不是枚举器访问元素 。

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

转载地址:http://qgdnb.baihongyu.com/

你可能感兴趣的文章