Перебор списка и создание сводных строк на лету - PullRequest
2 голосов
/ 17 октября 2008

РЕДАКТИРОВАТЬ: я упустил важный момент: .NET 2.0

Рассмотрим случай, когда у меня есть список несортированных элементов, для простоты типа такого:

class TestClass
{
    DateTime SomeTime;
    decimal SomePrice;

    // constructor
}

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

Примите данные этого теста:

List<TestClass> testList = new List<TestClass> { 
new TestClass(new DateTime(2008,01,01), 12),
new TestClass(new DateTime(2007,01,01), 20),
new TestClass(new DateTime(2008,01,01), 18)
};

Желаемый результат будет примерно таким:

2007-01-01: 
20
Total: 20

2008-01-01: 
12
18
Total: 30

Как лучше всего подходить к таким сценариям? В случае такого списка я бы реализовал интерфейс IComparable для TestClass, чтобы список можно было отсортировать.

Для создания самого отчета можно использовать что-то подобное (предположим, что у нас есть методы для таких задач, как накопление цен, отслеживание текущей даты и т. Д.):

for (int i=0;i<testList.Count;i++)
{
    if (IsNewDate(testList[i]))
    {
        CreateSummaryLine();
        ResetValuesForNewDate();
    }

    AddValues(testList[i]);
}

// a final summary line is needed to include the data for the last couple of items.
CreateSummaryLine();

Это работает нормально, но у меня странное ощущение, что касается второго "CreateSummaryLines".

Каким образом вы справляетесь с такими ситуациями (особенно учитывая тот факт, что нам нужно работать с элементами List <>, а не с предварительно категорированным словарем или чем-то подобным)?

Ответы [ 6 ]

3 голосов
/ 17 октября 2008

Хорошо, так что если вы не можете использовать LINQ:

(я использую var для экономии места, но при необходимости легко перевести на C # 2.0 ...)

var grouped = new SortedDictionary<DateTime, List<TestClass>>();
foreach (TestClass entry in testList) {
  DateTime date = entry.SomeTime.Date;
  if (!grouped.ContainsKey(date)) {
    grouped[date] = new List<TestClass>();
  }
  grouped[date].Add(entry);
}

foreach (KeyValuePair<DateTime, List<TestClass>> pair in testList) {
  Console.WriteLine("{0}: ", pair.Key);
  Console.WriteLine(BuildSummaryLine(pair.Value));
}
2 голосов
/ 17 октября 2008

[править] Поскольку вы используете .NET 2.0 с C # 3.0, вы можете использовать LINQBridge , чтобы включить это.

LINQ; что-то вроде:

        var groups = from row in testList
                  group row by row.SomeTime;
        foreach (var group in groups.OrderBy(group => group.Key))
        {
            Console.WriteLine(group.Key);
            foreach(var item in group.OrderBy(item => item.SomePrice))
            {
                Console.WriteLine(item.SomePrice);
            }
            Console.WriteLine("Total" + group.Sum(x=>x.SomePrice));
        }
2 голосов
/ 17 октября 2008

Важный вопрос: используете ли вы .NET 3.5, что позволяет использовать LINQ? Если это так, вы можете группировать по SomeTime.Date, а затем обрабатывать каждую дату отдельно.

Если у вас огромные объемы данных, мой Push LINQ проект может оказаться полезным, но в остальном прямая LINQ to Objects - ваш лучший выбор.

1 голос
/ 17 октября 2008

Какую версию C # / .NET вы используете? Если вы в курсе, взгляните на LINQ. Это позволяет группировать ваши данные. В вашем случае вы можете сгруппировать по дате:

var days = from test in testList group test by test.SomeTime;
foreach (var day in days) {
    var sum = day.Sum(x => x.SomePrice);
    Report(day, sum);
}

Таким образом, вы можете сообщить список значений для каждого day вместе с суммой денег за этот день.

0 голосов
/ 17 октября 2008

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

Если у вас есть только один отчет, не беспокойтесь о нем, пока не напишите второй, , но затем начните работать над архитектурой отчетности, которая упрощает расширение и обслуживание.

Например (и используя код @Marc Gravell):

public interface IReport
{
   string GetTextReportDocument();
   byte[] GetPDFReportDocument();  // needing this triggers the development of interface
}

public class SalesReport : IReport
{
   public void AddSale( Sale newSale )
   {
       this.Sales.Add(newSale);  // or however you implement it
   }

   public string GetTextReportDocument()
   {
       StringBuilder reportBuilder = new StringBuilder();
       var groups = from row in this.Sales
                    group row by row.SomeTime.Date;
       foreach (var group in groups.OrderBy(group => group.Key))
       {            
          reportBuilder.AppendLine(group.Key);
          foreach(var item in group.OrderBy(item => item.SomePrice))            
          {
             reportBuilder.AppendLine(item.SomePrice);
          }
          reportBuilder.AppendLine("Total" + group.Sum(x=>x.SomePrice));
      }

      return reportBuilder.ToString();
   }

   public byte[] GetPDFReportDocument()
   {
        return PDFReporter.GenerateDocumentFromXML( this.ConvertSalesToXML() );
   }

... остальное оставлено в качестве упражнения для читателя ...

0 голосов
/ 17 октября 2008

По вашему желаемому выводу вы хотите создать структуру данных, которая будет представлять ваши сводные данные. Вы хотите связать дату со списком объектов TestClass, а затем иметь коллекцию из них. Вы можете использовать HashSet или словарь для коллекции.

Для краткости, структура парных данных может реализовать ToString для вас.

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