Reputation: 27608
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
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
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
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
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
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
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