Как мне написать запрос LINQ, который инвертирует группировку иерархического источника данных? - PullRequest
4 голосов
/ 17 ноября 2009

Как написать запрос LINQ, который берет иерархические исходные данные и преобразует их так, что группировка инвертируется?

Скажем, у меня есть список объектов Темы, каждый из которых содержит коллекцию тегов, представляющих теги метаданных по этой теме. Мне нужно написать запрос LINQ, чтобы по существу перевернуть иерархию наизнанку, чтобы у меня был список тегов, в каждом из которых есть набор тем, помеченных этим конкретным тегом.

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

Я хочу, чтобы приведенные выше данные выглядели как результаты, приведенные ниже.

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 }

Можно предположить, что любой повторяющийся фрагмент данных является уникальным в том смысле, что он является единственным экземпляром в памяти, и это всего лишь несколько ссылок на один и тот же объект. Также для ответа разумно использовать анонимные классы для создания проекции, так как я понимаю, что форма классов может немного отличаться после инверсии.

ОБНОВЛЕНИЕ: ниже я добавил код, который устанавливает данные примера. Я играю с опубликованными ответами и некоторыми своими идеями в 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),
    },
};

Ответы [ 3 ]

4 голосов
/ 17 ноября 2009

То, что вы ищете, это Pivot.

Можно ли сводить данные с помощью LINQ?

Этот источник содержит код C # для метода расширения Linq Pivot:

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

}
0 голосов
/ 17 ноября 2009

Немного поиграв в LinqPad, думаю, я нашел подходящее решение.

Вот простой пример.

var topicsByTags = 
    from topic in topics
    from tag in topic.Tags
    group topic by tag;

И чтобы избавиться от избыточной коллекции тегов по каждой теме, мы можем сделать следующее.

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

ОБНОВЛЕНИЕ: Ниже приведена еще одна альтернатива, которая использует преимущества самой группировки в проекции. Вверху чуть более чистый запрос, а в минусе - ключ группы остается с группой, даже если она не будет использоваться.

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

Я воздержусь от принятия своего собственного ответа, чтобы дать некоторые дебаты о том, какое решение решает проблему, которую я поставил лучше всего.

0 голосов
/ 17 ноября 2009
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);
...