Domanda

EDIT: ho perso un punto cruciale: .NET 2.0

Considera il caso in cui ho un elenco di elementi non ordinati, per semplicità di un tipo come questo:

class TestClass
{
    DateTime SomeTime;
    decimal SomePrice;

    // constructor
}

Devo creare un output simile a un report, in cui vengono accumulati i prezzi totali per ogni giorno. Dovrebbe esserci una riga per ogni elemento, riempito dalle righe di riepilogo appropriate.

Prendi questi dati di prova:

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

L'output desiderato sarebbe qualcosa del genere:

2007-01-01: 
20
Total: 20

2008-01-01: 
12
18
Total: 30

Qual è il modo migliore per affrontare tali scenari? Nel caso di tale elenco, implementerei l'interfaccia IComparable per TestClass, in modo che l'elenco possa essere ordinato.

Per creare il rapporto stesso, si potrebbe usare qualcosa del genere (supponiamo che abbiamo metodi per attività come accumulare i prezzi, tenere traccia della data corrente ecc.):

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

Funziona bene, ma ho una strana sensazione per quanto riguarda il secondo " CreateSummaryLines " è preoccupato.

In che modo gestisci tali situazioni (soprattutto considerando il fatto, dobbiamo lavorare con un Elenco < > di elementi piuttosto che un dizionario pre-categorizzato o qualcosa del genere)?

È stato utile?

Soluzione

[modifica] Poiché si utilizza .NET 2.0 con C # 3.0, è possibile utilizzare LINQBridge per abilitare questo.

LINQ; qualcosa come:

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

Altri suggerimenti

Ok, quindi se non puoi usare LINQ:

(Sto usando var per risparmiare spazio, ma è facile da tradurre in C # 2.0 se necessario ...)

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

Domanda cruciale: stai usando .NET 3.5, permettendo così l'utilizzo di LINQ? In tal caso, puoi raggruppare per SomeTime.Date e quindi elaborare ciascuna data separatamente.

Se disponi di enormi quantità di dati, potresti trovare il mio Push LINQ progetto utile - ma per il resto LINQ to Objects è la soluzione migliore.

Quale versione di C # /. NET stai usando? Se sei aggiornato, dai un'occhiata a LINQ. Ciò consente di raggruppare i dati. Nel tuo caso, puoi raggruppare per data:

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

In questo modo è possibile segnalare un elenco di valori per ogni day, insieme alla somma di denaro per quel giorno.

L'output desiderato sembra voler creare una struttura di dati che rappresenti i dati di riepilogo. Si desidera associare una data a un elenco di oggetti TestClass, quindi disporre di una raccolta di questi. È possibile utilizzare un HashSet o un dizionario per la raccolta.

Per il riepilogo, la struttura dei dati delle coppie può implementare ToString per te.

Indipendentemente dal modo in cui si genera l'output del report, se lo si farà regolarmente nella propria applicazione, considerare la possibilità di mettere insieme un'interfaccia del report e implementare le classi per questa interfaccia per localizzare la logica per la creazione del report in un posto anziché lo scattering il codice per creare report in tutta l'applicazione. Nella mia esperienza, le applicazioni in genere hanno (o crescono) requisiti per più report e, infine, più formati.

Se hai solo un rapporto, non preoccuparti fino a quando non scrivi il secondo, ma poi inizia a lavorare su un'architettura di rapporto che semplifichi l'estensione e la gestione.

Ad esempio (e usando il codice di @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() );
   }

... il resto è lasciato come esercizio per il lettore ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top