以非对称方式合并两个Dictionary <string,string =“”>

时间:2016-03-04 09:12:13

标签: c# linq dictionary merge

我在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表达式没问题),有没有一种聪明的方法来处理这个问题?

4 个答案:

答案 0 :(得分:1)

您可以使用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))而不是之前执行。

可能是最简单,最易读的方法,&#34; LINQ left-outer-join&#34;:

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

答案 1 :(得分:1)

您可以像这样使用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);

这里的诀窍就是GroupJoin吧!这样,bar中的所有内容都会出现在最终结果中,对于相同的键,连接结果将是来自第二个集合的匹配结果的IEnumerable,在您的情况下是foo,因为它是Dictionary所以匹配的结果将只包含一个元素,您需要做的就是获取其值。如果不匹配(条形但不在foo中),匹配的结果集合将为空,因此FirstOrDefault()将返回默认值KeyValuePair<string, string>,其中Key和Value都设置为null。所以在这种情况下,我们只需从第一个集合中获取Value(在您的案例栏中)。

答案 2 :(得分:1)

(简单)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]);

答案 3 :(得分:1)

您的逻辑可以简化为:

结果将包含来自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; });