Reputation:
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]
}]
},
]
Upvotes: 0
Views: 199
Reputation: 117057
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 { k1 = kv1.Key, k2 = kv2.Key, v = kv2.Value}
)
.ToLookup(x => x.k2)
.ToDictionary(x => x.Key, x => x.ToDictionary(y => y.k1, y => y.v));
Upvotes: 0
Reputation: 74605
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
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.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
Upvotes: 1