Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge Dictionary<TKey, TValue> with Enumerable.Union method

I'm testing the UNION method to merge to dictionaries (of type Dictionary). It works fine with TValue type is string or int or even object. But if TValue type is a collection (tested with List and object[]) an exception is thrown : "ArgumentException: An item with the same key has already been added."

Here is my code :

Dictionary<int,string> _dico1 = new Dictionary<int, string>()
{
    {0, "zero"},
    {1, "one"}
};

Dictionary<int,string> _dico2 = new Dictionary<int,string>()
{
    {1 , "one"},
    {2 , "two"},
    {3 , "three"},
    {4 , "four"},
    {5 , "five"},
    {6 , "six"}
};

Dictionary<int, List<string>> _dico3 = new Dictionary<int, List<string>>()
{
    {0, new List<string>{"zero"}},
    {1, new List<string>{"one"}}
};

Dictionary<int, List<string>> _dico4 = new Dictionary<int, List<string>>()
{
    {1, new List<string>{"one"}},
    {2, new List<string>{"two"}},
    {3, new List<string>{"three"}},
    {4, new List<string>{"four"}},
    {5, new List<string>{"five"}},
    {6, new List<string>{"six"}},
};

    // works fine
    var mergeDico = _dico1.Union(_dico2).ToDictionary(key => key.Key, value => value.Value);

    // throw an ArgumentException : An item with the same key has already been added
    var mergeDico2 = _dico3.Union(_dico4).ToDictionary(key => key.Key, value => value.Value);

Why the behavior is not the same ? And How to resolve this problem ?

Thank you !

like image 322
Florian Avatar asked Jan 25 '11 09:01

Florian


3 Answers

In the first case, the Union is discarding the duplicate keys because the key/value pairs themselves are equal. In the second case they're not, because a List<String>{"one"} isn't equal to another List<string>{"one"}.

I suspect you want your Union call to use an IEqualityComparer which only takes account of the keys within the dictionary.

like image 81
Jon Skeet Avatar answered Nov 03 '22 19:11

Jon Skeet


You can merge second pair of dictionaries with code like this:


var mergeDico2 = _dico3
    .Concat(_dico4)
    .GroupBy(_=> _.Key, _ => _.Value)
    .ToDictionary(
        group => group.Key,
        group => group.SelectMany(_ => _).ToList());

It will produce a new dictionary where each value is a result of concatenating lists from values of both dictionaries. If you need only distinct elements of lists you can change ToDictionary call to this one:


var mergeDico2 = _dico3
    .Concat(_dico4)
    .GroupBy(_=> _.Key, _ => _.Value)
    .ToDictionary(
        group => group.Key,
        group => group.SelectMany(_ => _).Distinct().ToList());

like image 38
Konstantin Oznobihin Avatar answered Nov 03 '22 19:11

Konstantin Oznobihin


As Jon mentioned already that we need to implement IEqualityComparer to resolve the above issue. Here is the code how it can be done:

IEqualityComparer:

public class MyEqualityComparer : IEqualityComparer<KeyValuePair<int,List<string>>>
{
    public bool Equals(KeyValuePair<int, List<string>> x, KeyValuePair<int, List<string>> y)
    {
        //Let's say we are comparing the keys only.
        return x.Key == y.Key;
    }

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

Usage:

Dictionary<int, List<string>> _dico3 = new Dictionary<int, List<string>>()
    {
        {0, new List<string> {"zero"}},
        {1, new List<string> {"one"}}
    };

Dictionary<int, List<string>> _dico4 = new Dictionary<int, List<string>>()
    {
        {1, new List<string> {"one"}},
        {2, new List<string> {"two"}},
        {3, new List<string> {"three"}},
        {4, new List<string> {"four"}},
        {5, new List<string> {"five"}},
        {6, new List<string> {"six"}},
    };

Dictionary<int, List<string>> mergeDico2 = _dico3.Union(_dico4, new MyEqualityComparer())
    .ToDictionary(x => x.Key, x => x.Value);
like image 1
Raghav Avatar answered Nov 03 '22 20:11

Raghav