wageoghe
wageoghe

Reputation: 27608

Combine two Dictionaries with LINQ

My question has been flagged as a possible duplicate of this question: How to combine two dictionaries without looping?

I believe my question is different because I am asking how to combine two dictionaries in a particular way: I want all items from Dictionary1 plus all items from Dictionary2 that are not in (ie the key does not exist) in Dictionary1.

I have two dictionaries like this:

var d1 = new Dictionary<string,object>();
var d2 = new Dictionary<string,object>();

d1["a"] = 1;
d1["b"] = 2;
d1["c"] = 3;

d2["a"] = 11;
d2["e"] = 12;
d2["c"] = 13;

I would like to combine them into a new Dictionary (technically, it does not have to be a dictionary, it could just be a sequence of KeyValuePairs) such that the output contains all of the KeyValuePairs from d1 and only the KeyValuePairs from d2 whose Key does not appear in d1.

Conceptually:

var d3 = d1.Concat(d2.Except(d1))

But that is giving me all of the elements from d1 and d2.

Seems like it should be obvious, but I must be missing something.

Upvotes: 29

Views: 35172

Answers (6)

Thibault Lemaire
Thibault Lemaire

Reputation: 401

Well I don't know if it's a new feature in LinQ, but that's exactly what .Union() does :

var d3 = d1.Union(d2);

Of course with Dictionaries you'll have to give a custom equality comparer to match only the keys :

class KeyValuePairComparer<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>
{
    public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
    {
        return x.Key.Equals(y.Key);
    }
    public int GetHashCode(KeyValuePair<TKey, TValue> x)
    {
        return x.GetHashCode();
    }
}

and then :

var d3 = d1.Union(d2, new KeyValuePairComparer<string, object>());

With your example, the output would be (tested in C# interactive) :

> d1.Union(d2, new KeyValuePairComparer<string, object>())
UnionIterator { { "a", 1 }, { "b", 2 }, { "c", 3 }, { "e", 12 } }

Note the difference :

> d2.Union(d1, new KeyValuePairComparer<string, object>())
UnionIterator { { "a", 11 }, { "e", 12 }, { "c", 13 }, { "b", 2 } }

Upvotes: 8

BSharp
BSharp

Reputation: 976

Another solution using your own IEqualityComparer like in the answer of @bitxwise and @DaveShaw, but not using Except() which makes it a little simpler:

var d3 = d1.Concat(d2).Distinct(new MyComparer());

Upvotes: 1

SP007
SP007

Reputation: 1921

var d3 = d1.Concat(d2.Where(kvp => ! d1.ContainsKey(kvp.Key)))
           .ToDictionary(x => x.Key, x => x.Value);

This is working for me.

Upvotes: 14

DaveShaw
DaveShaw

Reputation: 52798

Jon Skeet (as usual) has an extension method allowing you to do this: Can I specify my explicit type comparator inline?

Upvotes: 2

bitxwise
bitxwise

Reputation: 3594

You can also use your own IEqualityComparer. Example below:

public class MyComparer : IEqualityComparer<KeyValuePair<string,string>> {
    public bool Equals(KeyValuePair<string, string> x, KeyValuePair<string, string> y) {
        return x.Key.Equals(y.Key);
    }

    public int GetHashCode(KeyValuePair<string, string> obj) {
        return obj.Key.GetHashCode();
    }
}

...

Dictionary<string, string> d1 = new Dictionary<string, string>();
d1.Add("A", "B");
d1.Add("C", "D");

Dictionary<string, string> d2 = new Dictionary<string, string>();
d2.Add("E", "F");
d2.Add("A", "D");
d2.Add("G", "H");

MyComparer comparer = new MyComparer();

var d3 = d1.Concat(d2.Except(d1, comparer));
foreach (var a in d3) {
    Console.WriteLine("{0}: {1}", a.Key, a.Value);
}

Upvotes: 1

Mark Byers
Mark Byers

Reputation: 838236

When you use Except by default it uses the default equality comparer, which for the KeyValuePair type compares both the keys and the values. You could this approach instead:

var d3 = d1.Concat(d2.Where(kvp => !d1.ContainsKey(kvp.Key)));

Upvotes: 43

Related Questions