How do I write a LINQ query that inverts the grouping of a hierarchical data source?

☆樱花仙子☆ 提交于 2019-12-21 02:41:36

问题


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 which contains a collection of Tags which represent meta-data tags on that topic. What I need is to write a LINQ query to basically flip the hierarchy inside out so that I have a list of Tags each of which have a collection of topics that are tagged with that particular tag.

Topic { Title = "Political Debate #1", Posted = 01/02/2008 }
   Tag { Name = "Contraversial", Color = "Red" }
   Tag { Name = "Politics", Color = "LightBlue" }
Topic { Title = "iPhone to support SiliverLight!", Posted = 02/23/2009 }
   Tag { Name = "BleedingEdge", Color = "LightBlue" }
   Tag { Name = "Contraversial", Color = "Red" }
   Tag { Name = ".NET", Color = "LightGreen" }
Topic { Title = "Fed Chairman admits guilt for causing second Great Depression", Posted = 06/15/2010 }
   Tag { Name = "Politics", Color = "LightBlue" }
   Tag { Name = "Contraversial", Color = "Red" }

I want the above data to look instead like the results below.

Tag { Name = "Contraversial", Color = "Red" }
    Topic { Title = "Political Debate #1", Posted = 01/02/2008 }
    Topic { Title = "iPhone to support SiliverLight!", Posted = 23/02/2009 }
    Topic { Title = "Fed Chairman admits guilt for causing second Great Depression", Posted = 06/15/2010 }
Tag { Name = "Politics", Color = "LightBlue" }
    Topic { Title = "Political Debate #1", Posted = 01/02/2008 }
    Topic { Title = "Fed Chairman admits guilt for causing second Great Depression", Posted = 06/15/2010 }
Tag { Name = ".NET", Color = "LightGreen" }
    Topic { Title = "iPhone to support SiliverLight!", Posted = 23/02/2009 }

You can assume that any repeated piece of data is referentially unique in that that is a single instance in memory and these there are just several references to that same object. Also it is reasonable for the answer to use anonymous classes to produce the projection since I realize the shape of the classes may be slightly different after the inversion.

UPDATE: I added the code below which sets up the example data. I'm playing around with the answers posted and some of my own ideas in LinqPad.

var tags = new[]
{
    new { Name = "Contraversial", Color = "Red" },
    new { Name = "Politics", Color = "LightBlue" },
    new { Name = ".NET", Color = "LightGreen" },
    new { Name = "BleedingEdge", Color = "LightBlue" }

};

var topics = new[]
{
    new 
    { 
        Title = "Political Debate #1", 
        Posted = DateTime.Parse("01/02/2008"), 
        Tags = (from t in tags where new []{"Contraversial", "Politics"}.Contains(t.Name) select t),
    },
    new 
    { 
        Title = "iPhone to support SiliverLight!", 
        Posted = DateTime.Parse("02/23/2009"), 
        Tags = (from t in tags where new []{"BleedingEdge", "Contraversial", ".NET", }.Contains(t.Name) select t),
    },
    new 
    { 
        Title = "Fed Chairman admits guilt for causing second Great Depression", 
        Posted = DateTime.Parse("06/15/2010"), 
        Tags = (from t in tags where new []{"Contraversial", "Politics"}.Contains(t.Name) select t),
    },
};

回答1:


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;
    }

}



回答2:


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);



回答3:


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.



来源:https://stackoverflow.com/questions/1744837/how-do-i-write-a-linq-query-that-inverts-the-grouping-of-a-hierarchical-data-sou

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!