以非对称方式合并两个字典<字符串,字符串>
本文关键字:字符串 字典 两个 方式 非对称 合并 | 更新日期: 2023-09-27 18:37:16
我在 C# 6.0 中有两个字典,我想以一种聪明的方式合并它们。
将第一本字典foo
取为:
var foo = new Dictionary<string, string>
{
{"a", "10"},
{"b", "20"},
{"c", "30"},
};
第二本字典bar
为:
var bar = new Dictionary<string, string>
{
{"a", "333"},
{"e", "444"},
{"f", "555"},
};
我想用这个逻辑将它们合并到一个字典中:
- 如果键在
foo
中但不在bar
则在新字典中忽略它 - 如果一个键在
bar
中但不在foo
中,则在新字典中获取它 - 如果一个键同时在
foo
和bar
中,则在新字典中取foo
的值
这是我的预期结果:
var result = new Dictionary<string, string>
{
{"a", "10"}, //this comes from foo
{"e", "444"}, //this comes from bar
{"f", "555"}, //this comes from bar
};
有没有一种聪明的方法可以在没有 forloop 的情况下处理这个问题(LINQ 表达式很好)?
您可以使用HashSet<T>
方法和 LINQ:
1)
var fooKeys = new HashSet<string>(foo.Keys);
var barKeys = new HashSet<string>(bar.Keys);
fooKeys.IntersectWith(barKeys); // remove all from fooKeys which are not in both
barKeys.ExceptWith(fooKeys); // remove all from barKeys which are remaining in fooKeys and also in barKeys
Dictionary<string, string> result = fooKeys
.Select(fooKey => new KeyValuePair<string, string>(fooKey, foo[fooKey]))
.Concat(barKeys.Select(bKey => new KeyValuePair<string, string>(bKey, bar[bKey])))
.ToDictionary(kv => kv.Key, kv => kv.Value);
这是安全的,因为两者都相互排斥。它也非常有效,因为这些HashSet
方法对两个集合具有O(n)
复杂性。
如果您认为无法理解,也许您更喜欢这个:
2)
var inBoth = from kv1 in foo
join kv2 in bar
on kv1.Key equals kv2.Key
select kv1;
var onlyInBar = bar.Keys.Except(foo.Keys)
.Select(b => new KeyValuePair<string, string>(b, bar[b]));
Dictionary<string, string> result = new Dictionary<string, string>();
foreach (var kv in inBoth.Concat(onlyInBar))
result.Add(kv.Key, kv.Value);
第一个查询使用连接(在查询语法中更具可读性),它仅返回第一个字典中的键值对,其中键也存在于第二个字典中。第二个查询使用 Enumerable.Except
从第一个字典中的所有字典中排除所有查询。Enumerable.Join
和Enumerable.Except
都使用引擎盖下的套装,因此它们非常高效。
值得注意的是,由于 LINQ 的延迟执行,这两个查询仅在foreach (var kv in inBoth.Concat(onlyInBar))
执行,而不是在之前执行。
可能是最简单和最易读的方法,即"LINQ 左外联接":
3)
KeyValuePair<string, string> defaultPair = default(KeyValuePair<string, string>);
var query = from barKv in bar
join fooKv in foo
on barKv.Key equals fooKv.Key into gj_bf
from bf in gj_bf.DefaultIfEmpty()
select bf.Equals(defaultPair) ? barKv : bf;
foreach (var kv in query)
result.Add(kv.Key, kv.Value);
像这样使用GroupJoin
:
var res =
bar
.GroupJoin(
foo,
kvp => kvp.Key,
kvp => kvp.Key,
(kvp, g) => new KeyValuePair<string, string>(kvp.Key, g.FirstOrDefault().Value ?? kvp.Value))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
这里的诀窍是用foo GroupJoin
酒吧! 这样,bar 中的所有内容都将出现在最终结果中,对于相同的键,连接的结果将是来自第二个集合的匹配结果的IEnumerable
,在您的情况下是 Foo,并且由于它是Dictionary
,因此匹配的结果将只包含一个元素,您需要做的就是获取其值。如果没有匹配项(项在栏中但不在 foo 中),匹配的结果集合将为空,因此FirstOrDefault()
将返回默认值 KeyValuePair<string, string>
,键和值都设置为 null。因此,在这种情况下,我们只是从第一个集合中获取值(在您的案例栏中)。
您的逻辑可以简化为:
结果将包含来自 bar
的所有键,如果存在,则取自foo
的值,否则取自bar
。
翻译过来就是这样:
var result = bar.ToDictionary(barItem => barItem.Key, barItem =>
foo.ContainsKey(barItem.Key) ? foo[barItem.Key] : barItem.Value);
或者更长一点,但更理想:
var result = bar.ToDictionary(barItem => barItem.Key, barItem =>
{ string fooValue; return foo.TryGetValue(barItem.Key, out fooValue) ? fooValue : barItem.Value; });
A (简单) Linq 解决方案:
var newDict = new Dictionary<string, string>();
var toIncludeFromFoo = bar.Keys.Intersect(foo.Keys).ToList();
toIncludeFromFoo.ForEach(x => newDict [x] = foo[x]);
var toAddFromBar = bar.Keys.Except(foo.Keys).ToList();
toAddFromBar.ForEach(x => newDict [x] = bar[x]);