Перебор списка и создание сводных строк "на лету"

StackOverflow https://stackoverflow.com/questions/211958

  •  03-07-2019
  •  | 
  •  

Вопрос

Редактировать:Я упустил важный момент:.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".

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

Это было полезно?

Решение

[редактировать] Поскольку вы используете .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));
        }

Другие советы

Хорошо, так что если вы не можете использовать 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));
}

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

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

Какую версию 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, а также сумму денег за этот день.

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

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

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

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

Например (и используя код @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() );
   }

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top