Home > Net >  C# Linq revers outer-key with inner-key for a dictionary with dictionary as value
C# Linq revers outer-key with inner-key for a dictionary with dictionary as value

Time:12-07

I'm trying to use Linq to group a dictionary of type Dictionary<string, Dictionary<string, List>> in such way so that the outer Key becomes the inner key and vice-versa.

This is the closest I've get

       Dictionary<IEnumerable<string>, Dictionary<string, List>> reversed = 
          nodeTypedContainer
            .GroupBy(kv =>
            {
                IEnumerable<string> keys = kv.Value.Keys.Select(x => x);

                return keys;
            }, kv =>
            {
                return new
                {
                    Values = kv.Value.Values.SelectMany(x => x.ToList()),
                    Node = kv.Key
                };
            })
            .ToDictionary(
            group =>
            {
                return group.Key;
            },
            group =>
            {
                return group
                     .Select(x => (x.Containers, x.Node))
                     .GroupBy(x => x.Node, x => x.Containers)
                     .ToDictionary(x => x.Key, x => x.SelectMany(q => q));
            });

where nodeTypedContainer is

Dictionary<string, Dictionary<string, IEnumerable<V1Container>>>

So the outer key is an IEnumerable, which makes sense because if I have a dictionary like this initially

[
   {
      key: node1,
      value: [{
           key: k1
           value: [1, 2, 3]
         },{
           key: k2
           value: [2]
       ]
   },
   {
      key: node2,
      value: [{
           key: k1
           value: [3]
      }]
   }
]

reversed shoud be

[
   {
      key: k1,
      value: [{
           key: node1
           value: [1, 2, 3]
      },{
           key: node2
           value: [3]
      }]
   },
   {
      key: k2,
      value: [{
           key: node1
           value: [2]
      }]
   },
]

CodePudding user response:

This is the most straightforward way I could come up with:

Dictionary<string, Dictionary<string, List<int>>> reversed = 
(
    from kv1 in nodeTypedContainer
    from kv2 in kv1.Value
    select new { x = kv1.Key, y = kv2.Key, z = kv2.Value}
)
    .ToLookup(q => q.y)
    .ToDictionary(q => q.Key, q => q.ToDictionary(w => w.x, w => w.z));

CodePudding user response:

Interesting one, I thought.. I'd use SelectMany to expand the nest to a { key1, key2, value } and then Aggregate to put it back together rather than GroupBy/ToDictionary

var r = nodeTypedContainer
    .SelectMany(
        kvpO => kvpO.Value, 
        (kvpO, kvpI) => new { KeyO = kvpO.Key, KeyI = kvpI.Key, kvpI.Value }
    )
    .Aggregate(
        new Dictionary<string, Dictionary<string, List<int>>>(),
        (seed, val) => {
            if (seed.TryGetValue(val.KeyI, out var dictI))
                dictI.Add(val.KeyO, val.Value);
            else
                seed[val.KeyI] = new() { { val.KeyO, val.Value } };
            return seed;
        }
    );

Aggregate and to a lesser degree SelectMany, aren't often used I think, so it might warrant a bt of explanation.

SelectMany with one argument is quite simple: it converts a T[][] into a T[] so some nested list of lists (think like a list of people and each person has a list of pets) becomes a straight list of the nested items (10 people each with 20 pets in a list, becomes 1 list of 200 pets).

SelectMany with two arguments allows us access to the original person as well as the list of pets, which means we can access the higher level of nest as well as the lower. This means we can turn out a list of 200 pets, with each person repeated 20 times too

In this case it converts the data:

[
   {
      key: node1,
      value: [{
           key: k1
           value: [1, 2, 3]
         },{
           key: k2
           value: [2]
       ]
   },
   {
      key: node2,
      value: [{
           key: k1
           value: [3]
      }]
   }
]

Into something like:

{ node1, k1, [1, 2, 3] }
{ node1, k2, [2] }
{ node2, k1, [3] }

No hierarchy now; repeating node1 instead.

Then we put it back together using Aggregate

  • The first argument to Aggregate is the new Dictionary<string, Dictionary<string, List<int>>> that we will output. It starts out as an empty dictionary, and we'll build it as we loop over every unnested item.
  • The second argument to Aggregate should be some code that modifies the current accumulating value, and returns it. We don't actually need a version that returns it because we always modify the contents of the seed we created rather than having an immutable style "take in the current iteration, prepare a new version based on it and return it for next time". To some extent it goes against "LINQ should not have side effects" but it's generally accepted at Aggregate can have this side effect and it's safe in the sense that we're modifying the instance we created in the seed. Be wary of using Aggregate on something mutable that was created elsewhere other than in the first argument

So the lambda for the second arg receives the new Dict<Dict>; it has to look up whether that outer dictionary contains the inner key (like k1, k2).. If it does then it should add the outer key and inner list as a new entry. If it doesn't then it should create a new inner Dictionary<string, List> initialized with the outer key and the inner List

  • Related