Vũ Bích
Vũ Bích

Reputation: 51

Group dictionaries with Linq

I have two List<Dictionary<string, string>>

List<Dictionary<string, string>> ListdicA = new List<Dictionary<string, string>>();
Dictionary<string, string> dic1 = new Dictionary<string, string>();
dic1.Add("Type1", "1");
dic1.Add("Type2", "A");
dic1.Add("Type3", "X");
Dictionary<string, string> dic2 = new Dictionary<string, string>();
dic2.Add("Type1", "2");
dic2.Add("Type2", "B");
dic2.Add("Type3", "Y");
ListdicA.Add(dic1);
ListdicA.Add(dic2);

List<Dictionary<string, string>> ListdicB = new List<Dictionary<string, string>>();
Dictionary<string, string> dic3 = new Dictionary<string, string>();
dic3.Add("Type1", "1");
dic3.Add("Type2", "C");
dic3.Add("Type3", "X");
Dictionary<string, string> dic4 = new Dictionary<string, string>();
dic4.Add("Type1", "2");
dic4.Add("Type2", "D");
dic4.Add("Type3", "Z");
ListdicB.Add(dic3);
ListdicB.Add(dic4);

I want to merge the two lists and group the values by their keys. The result is a List<Dictionary<string, List<string>>>. First, I want to merge the two lists:

Type1 1
Type2 A
Type3 X

Type1 2
Type2 B
Type3 Y

Type1 1
Type2 C
Type3 X

Type1 2
Type2 D
Type3 Z

And I want group it by the keys, the result look like:

Type1 1
Type2 A C
Type3 X X

Type1 2
Type2 B D
Type3 Y Z

Upvotes: 2

Views: 813

Answers (2)

Gert Arnold
Gert Arnold

Reputation: 109079

If you do

ListdicA.Zip(ListdicB, (a,b) => a.Concat(b)
.GroupBy(x => x.Key)
.Select (g => new
              {
                   g.Key, 
                   Values = g.SelectMany(d => d.Value).Distinct()
              } ))

you get this result:

Type 1   1
Type 2   A
         C
Type 3   X

Type 1   2
Type 2   B
         D
Type 3   Y
         Z

I'm not sure whether your X X result is deliberate, or a typo. If it's intended I should add some more logic.

Upvotes: 0

M.kazem Akhgary
M.kazem Akhgary

Reputation: 19149

Based on Question Update:

This is very complex (at least it was for me). I showed the steps and also i put each LINQ inside parenthesis which do the part of that job.

1) You have to join two lists. (ListdicA.Concat(ListdicB))

2) Group all Dictionaries by Value of Type1 key in each dictionaries.(GroupBy(x => x["Type1"]))

3) Unwrap all Items IGrouping<string,Dictionary<string,string>> to KeyValuePair<string,string> (SelectMany(y => y))

4) Again Group key value pairs by Key so that you join values of same keys together (GroupBy(y => y.Key))

5) Unwrap IGrouping<string,KeyValuePair<string,string>> into IEnumerable<KeyValuePair<string,List<string>>> (Select(y => new KeyValuePair<string, List<string>>(y.Key, y.Select(z => z.Value).ToList())))

6) Convert IEnumerable<IEnumerable<KeyValuePair<string,List<string>>>> into IEnumerable<Dictionary<string,List<string>>> (Select(x => x.ToDictionary(y => y.Key, y => y.Value)))

7) Finally Convert IEnumerable<Dictionary<string,List<string>>> into List<Dictionary<string, List<string>>> (ToList())

string groupby = "Type1";

List<Dictionary<string, List<string>>> result =
    ListdicA.Concat(ListdicB).GroupBy(x => x[groupby]).Select(x =>
        x.SelectMany(y => y) // Put .Distinct() here to remove duplicates.(optional)
            .GroupBy(y => y.Key).Select(y => new KeyValuePair<string, List<string>>(y.Key, y.Select(z => z.Value).ToList())))
                .Select(x => x.ToDictionary(y => y.Key, y => y.Value)).ToList();

foreach (var d in result)
{
    foreach (var k in d)
    {
        Console.Write(k.Key + " : ");
        foreach (var l in k.Value)
        {
            Console.Write(l + " ");
        }
        Console.WriteLine();
    }
}

Outputs

Type1 1 1
Type2 A C
Type3 X X

Type1 2 2
Type2 B D
Type3 Y Z

As I noted in comment (inside code) if you remove that comment and put .Distinct() right there it will remove duplicates

List<Dictionary<string, List<string>>> result =
    ListdicA.Concat(ListdicB).GroupBy(x => x[groupby]).Select(x => x.SelectMany(y => y)
        .Distinct().GroupBy(y => y.Key)
            .Select(y => new KeyValuePair<string, List<string>>(y.Key, y.Select(z => z.Value).ToList())))
                .Select(x => x.ToDictionary(y => y.Key, y => y.Value)).ToList();

will output.

Type1 1
Type2 A C
Type3 X

Type1 2
Type2 B D
Type3 Y Z

If you dont want to loose X then you need a custom Distinct. You will need this class. (linq was taken and edited from here)

public static class SomeMoreLinq
{
    public static IEnumerable<TSource> DistinctIf<TSource>
        (this IEnumerable<TSource> source, Func<TSource, bool> keySelector)
    {
        HashSet<TSource> seenKeys = new HashSet<TSource>();
        foreach (TSource element in source)
        {
            if (seenKeys.Add(element) || !keySelector(element))
            {
                yield return element;
            }
        }
    }
}

And then use it in your query.

List<Dictionary<string, List<string>>> result =
    ListdicA.Concat(ListdicB).GroupBy(x => x[groupby]).Select( x => x.SelectMany(y => y)
        .DistinctIf(y => y.Key == groupby).GroupBy(y => y.Key)
            .Select(y => new KeyValuePair<string, List<string>>(y.Key, y.Select(z => z.Value).ToList())))
                .Select(x => x.ToDictionary(y => y.Key, y => y.Value)).ToList();

will output.

Type1 1
Type2 A C
Type3 X X

Type1 2
Type2 B D
Type3 Y Z

If you have any questions feel free to ask.

While this completely melted my mind but it was a good practice!.

Upvotes: 1

Related Questions