tags:

views:

724

answers:

6

EDIT: I missed a crucial point: .NET 2.0

Consider the case where I have a list of unsorted items, for the sake of simplicity of a type like this:

class TestClass
{
    DateTime SomeTime;
    decimal SomePrice;

    // constructor
}

I need to create a report-like output, where the total prices for each day are accumulated. There should be one line for each item, folled by the appropriate summary lines.

Take this test data:

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

The desired output would be something like this:

2007-01-01: 
20
Total: 20

2008-01-01: 
12
18
Total: 30

What's the best way to approach such scenarios? In the case of such a list, I would implement the IComparable interface for TestClass, so that the list can be sorted.

To create the report itself, something like this could be used (let's assume that we have methods for tasks like accumulating the prices, keeping track of the current date etc):

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

This works alright, but I have a strange feeling as far as the second "CreateSummaryLines" is concerned.

In what ways do you handle such situations (especially considering the fact, the we need to work with a List<> of items rather than a pre-categorized Dictionary or something like that)?

+2  A: 

Crucial question: are you using .NET 3.5, thus allowing LINQ to be used? If so, you can group by SomeTime.Date and then process each date separately.

If you have massive amounts of data, you may find my Push LINQ project useful - but otherwise straight LINQ to Objects is your best bet.

Jon Skeet
Missed that important information: .NET 2.0 is to be used.
Grimtron
Then use LINQBridge since you seem to be using C# 3.0
Marc Gravell
+2  A: 

[edit] Since you are using .NET 2.0 with C# 3.0, you can use LINQBridge to enable this.

LINQ; something like:

        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));
        }
Marc Gravell
Missed that important information: .NET 2.0 is to be used.
Grimtron
I've added the 2.0 thing via LINQBridge
Marc Gravell
You might want to consider grouping by row.SomeTime.Date unless you've already done the work of truncating the times when creating the instances of TestClass.
tvanfosson
+1  A: 

What version of C#/.NET are you using? If you're up to date, have a look at LINQ. This allows grouping your data. In your case, you could group by date:

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

This way you could report a list of values for each day, along with the sum of money for that day.

Konrad Rudolph
Missed that important information: .NET 2.0 is to be used.
Grimtron
A: 

Your desired output feels like you want to make a data structure that represents your summary data. You want to pair a date with a list of TestClass objects, then have a collection of those. You could use a HashSet or a Dictionary for the collection.

For the summary, pair data structure can implement ToString for you.

plinth
+3  A: 

Okay, so if you can't use LINQ:

(I'm using var to save space, but it's easy to translate to C# 2.0 if necessary...)

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));
}
Jon Skeet
A: 

Regardless of how you generate the report output, if you are going to be doing this regularly in your application consider putting together a report interface and implementing classes for this interface to localize the logic for creating the report in one place rather than scattering the code to create reports throughout your application. In my experience, applications typically have (or grow) requirements for multiple reports and, eventually, multiple formats.

If you've only got one report, don't worry about it until you write your second one, but then start working on a reporting architecture that makes it easier to extend and maintain.

For example (and using @Marc Gravell's code):

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

... the rest is left as an exercise for the reader ...

tvanfosson