How would one write a LINQ query which takes a hierarchical source data and transforms it so that the grouping is inverted?
Say I have a list of Topic objects each of wh
What you're looking for is a Pivot.
Is it possible to Pivot data using LINQ?
This source contains C# code for a Linq Pivot extension method:
public static class LinqExtensions
{
public static Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>> Pivot<TSource, TFirstKey, TSecondKey, TValue>(this IEnumerable<TSource> source, Func<TSource, TFirstKey> firstKeySelector, Func<TSource, TSecondKey> secondKeySelector, Func<IEnumerable<TSource>, TValue> aggregate)
{
var retVal = new Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>>();
var l = source.ToLookup(firstKeySelector);
foreach (var item in l)
{
var dict = new Dictionary<TSecondKey, TValue>();
retVal.Add(item.Key, dict);
var subdict = item.ToLookup(secondKeySelector);
foreach (var subitem in subdict)
{
dict.Add(subitem.Key, aggregate(subitem));
}
}
return retVal;
}
}
IDictionary<Topic, IList<Tag>> data;
var n = data.SelectMany(x => x.Value.Select(y => new { Topic = x.Key, Tag = y }))
.GroupBy(x => x.Tag, x => x.Topic);
After playing around in LinqPad a bit I think I may have found a suitable solution.
Here is the simple example.
var topicsByTags =
from topic in topics
from tag in topic.Tags
group topic by tag;
And in order to get rid of the redundant Tags collection under each topic we can do the following.
var topicsByTags =
from topic in topics
from tag in topic.Tags
group new
{
Title = topic.Title,
Color = topic.Posted,
} by tag into g
select new
{
g.Key.Name,
g.Key.Color,
Topics = g,
};
UPDATE: Below is another alternative which takes advantage of the grouping itself in the projection. Upside is slightly cleaner query, downside is that the group Key sticks around with the group even if it's not going to be used.
var topicsByTags =
from topic in topics
from tag in topic.Tags
group new
{
Title = topic.Title,
Color = topic.Posted,
} by tag into g
select new
{
g.Key.Name,
g.Key.Color,
Topics = g,
};
I'll hold off accepting my own answer to allow some debate over which solution solves the problem that I posed the best.