以非对称方式合并两个字典<字符串,字符串>

本文关键字:字符串 字典 两个 方式 非对称 合并 | 更新日期: 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,则在新字典中获取它
  • 如果一个键同时在 foobar 中,则在新字典中取 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.JoinEnumerable.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]);