Имитировать функцию свертывания в общем списке - PullRequest
3 голосов
/ 29 октября 2010

У меня есть общий список типа Element, например.

public class Element
{
    public string Country { get; set; }
    public string City { get; set; }
    public int Population { get; set; }
}

Со следующими данными.

var elements = new List<Element>
   {
       new Element { Country = "Country A", City = "Barrie", Population = 12 },
       new Element { Country = "Country A", City = "Barrie2", Population = 12 },
       new Element { Country = "Country A", City = "Barrie2", Population = 12 },
       new Element { Country = "Country A", City = "Barrie", Population = 12 },
       new Element { Country = "Country D", City = "Essex", Population = 12 },
       new Element { Country = "Country A", City = "Barrie", Population = 12 },
       new Element { Country = "Country A", City = "Barrie", Population = 12 },
       new Element { Country = "Country D", City = "Essex", Population = 12 },
       new Element { Country = "Country A", City = "Barrie", Population = 12 },
       new Element { Country = "Country A", City = "Barrie", Population = 12 }
   };

По сути, я хотел бы получить промежуточный итогнаселение сгруппировано по стране и городу.

Что-то вроде.

Country A | Barrie  | `running total for Barrie`
Country A | Barrie2 | `running total for Barrie2`
          |         | `total for Country A`
Country D | Essex   | `running total for Essex`
          |         | `total for Country D`
          |         | `total for everything`

Я не мог найти расширение (я говорю расширение, потому что я планирую использовать накопительный пакет несколько раз) в любом месте, так что я решил, что я бы попробовал,Итак, я начал с этого простого запроса.

var groupedElements = elements
    .GroupBy(x => new { x.Country, x.City })
    .Select(x => new { Country = x.Key, City = x.Select(xx => xx.City), Population = x.Sum(xx => xx.Population) })
    .ToList();

Этот запрос работает, как и ожидалось, поэтому я думаю, что я на правильном пути.Затем я думаю, что мне нужно выяснить, какое свойство из groupedElements является агрегатным, потому что это то, над чем мы будем собираться.Как мне это сделать?Или, возможно, у меня может быть параметр, который заставляет меня указывать, в каком столбце я хочу выполнять агрегатную функцию.

1 Ответ

2 голосов
/ 29 октября 2010

Я не думаю, что это так легко сделать, как вы думаете. Во-первых, элементы нужной последовательности результатов не являются «естественно» одного типа. Вы хотите получить общее в каждой стране, сгруппированное общее для каждой страны, а затем общее общее.

Даже если бы вы могли написать краткий запрос для создания этой информации, вызывающая сторона должна была бы различать итоги каждого вида от результата. Интересно, имеет ли смысл вообще рассматривать результат, который вы хотите, как отдельный IEnumerable<XXX>? возможно, было бы гораздо разумнее создать хорошее ОО-решение, подобное этому, и перейти оттуда:

public interface IGeographicEnity
{
   string Name { get; }
   int Population { get; }
}

public class City : IGeographicEntity {...}

public class Country : IGeographicEntity
{
   public IList<City> Cities { get {...} }       
   public int Population { get { return Cities.Sum(c => c.Population); } }
   ...
}

public class World : IGeographicEntity
{
    public IList<Country> Countries { get {...} }       
    public int Population { get { return Countries.Sum(c => c.Population); }
    ...
}

Если вы все еще хотите придерживаться своей оригинальной идеи, вот лучшее, что я могу придумать:

public class PopulationTotal
{
    // You can use subclassing instead of an enum to represent this
    public enum Kind
    { RunningTotalWithinCountry, TotalForCountry, TotalOverall }

    public string GroupName { get; private set; }
    public int Value { get; private set; }
    public Kind Kind { get; private set; }

    public static IEnumerable<PopulationTotal> GetTotals(IEnumerable<Element> elements)
    {
        int overallTotal = 0;

        foreach (var elementsByCountry in elements.GroupBy(e => e.Country))
        {
            int runningTotalForCountry = 0;

            foreach (var element in elementsByCountry)
            {
                runningTotalForCountry += element.Population;
                yield return new PopulationTotal
                                    {
                                        GroupName = element.City,
                                        Kind = Kind.RunningTotalWithinCountry,
                                        Value = runningTotalForCountry
                                    };
            }

            overallTotal += runningTotalForCountry;

            yield return new PopulationTotal
                                {
                                    GroupName = elementsByCountry.Key,
                                    Kind = Kind.TotalForCountry,
                                    Value = runningTotalForCountry
                                };
        }

        yield return new PopulationTotal
                            {
                                GroupName = null,
                                Kind = Kind.TotalOverall,
                                Value = overallTotal
                            };
    }
}

Использование

var totals = PopulationTotal.GetTotals(elements);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...