Come valutare più confronti booleani e decimali in un ampio elenco di dati?

3

Ho un problema di progettazione e nessuno che mi aiuti a sfidarlo. Tranne te.

Il sistema gestisce i file di dati. Ogni riga può essere confrontata con un numero di criteri, scelti dall'utente. La riga è quindi vera o falsa per quel criterio.

Ogni criterio ha anche un prezzo e un costo (e un margine: prezzo - costo). Tuttavia, l'utente viene addebitato solo se una riga corrisponde a uno dei criteri. E pagano solo per uno dei criteri corrispondenti: quello usato dovrebbe essere quello con il margine più alto.

Strutturalmente, appare come questo in pseudo codice:

SystemRow
{
    bool Criteria1 { get; set; }
    bool Criteria2 { get; set; } //etc
}

Esiste un DTO separato che contiene i criteri selezionati dall'utente come Enum bit a bit. È passato in una funzione di elaborazione di riga insieme alla riga.

ProcessRow(selectedCriteria, row)
{
    if(selectedCriteria.HasFlag(CriteriaEnum.Criteria1)
    {
        // this sets row.Critera1 to true if it matches conditions
        EvaluateRowForCriteria1(row);
    }
    // then evaluate other criteria one by one
}

CalculatePrice(ListOfRows)
{
    decimal runningTotal;
    foreach(row in ListOfRows)
    {
       runningTotal += [the cost of one - and only one - matching criteria]
    }
}

Ciascuna di queste funzioni esiste in una classe separata, in conformità con SRP.

Posso pensare a diversi modi per farlo, ma ognuno è disordinato: difficile da mantenere e inefficiente.

In primo luogo, posso aggiungere una proprietà "Prezzo" e "Margine" a "Riga". Quindi, nella classe in cui vive "ProcessRow", posso prelevare tutti i prezzi e i margini dal DB e memorizzarli in variabili locali. Durante "ProcessRow" posso confrontare il Margine di un criterio con il Marign della riga e, se possibile, assegnare il prezzo. Quindi posso sommare i prezzi in CalculatePrice.

In secondo luogo, posso recuperare il prezzo e il margine in CalculatePrice. Quindi, per ogni riga, posso utilizzare il criterio selezionato X sulla riga, trovare quello corretto da addebitare e aggiungerlo al totale parziale.

Ciò che rende questo pasticcio è che ci sono circa 20 di questi criteri. Quindi, in ogni caso, ci sarà un sacco di variabili a dare il calcio d'inizio se non voglio andare al Db per ottenere i valori su ogni riga.

C'è un design migliore che posso sfruttare qui?

    
posta Matt Thrower 30.03.2017 - 17:40
fonte

2 risposte

1

Analisi del tuo approccio

Situazione attuale:

  • non conosci in anticipo i criteri desiderati dall'utente;
  • si mantengono i criteri da utilizzare per una query in un contenitore;
  • fai scorrere tutte le righe;
  • per ogni riga si cercano tutti i criteri di corrispondenza;
  • il tuo problema è di valutare ogni riga massimizzando il margine;

Le tue opzioni attuali sono:

  • opzione A: valuta la riga nel processo di abbinamento, modificando il prezzo se c'è un margine migliore per un altro criterio di corrispondenza;
  • opzione B: prezzo della riga alla sommatoria, tenendo traccia dei migliori criteri.

Poiché hai più di 20 criteri, entrambe le soluzioni implicano il controllo di tutti i criteri di corrispondenza. Nel peggiore dei casi, proverai ad abbinare tutte le 20 condizioni: 19 troppo!

Migliore alternativa

Invece di chiedersi quale sia il criterio più redditizio, e questo per ogni riga, ti propongo di:

  • ordina prima il tuo contenitore di criteri di selezione richiesti dall'utente, diminuendo l'ordine di redditività.
  • quindi per una data riga, prova successivamente ciascun criterio nell'ordine giusto. Quindi, quando un criterio corrisponde, è il più redditizio e non è più necessario abbinare i restanti criteri per la stessa linea.
  • puoi calcolare il prezzo quando elabori la linea o quando effettui la somma: in ogni caso hai solo un prezzo da utilizzare.

Questo approccio riduce considerevolmente il tempo di abbinamento per le righe riuscite. Il rendimento è invariato per le righe non selezionate.

    
risposta data 30.03.2017 - 20:05
fonte
1

Modella i tuoi criteri come una gerarchia di classi, non solo come un elenco di stringhe: ricava ogni classe di criteri da un'interfaccia comune o una classe di base astratta:

  interface ICriteria
  { 
      bool DoesItMatch(Row row);
      double Price{get;}
      double Cost{get;}
  }

Ora tutto ciò che serve è una classe factory che crea una lista di oggetti concreti ICriteria dalla selezione dell'utente (che presumo eseguirà una query sul db una sola volta). Dopo aver ottenuto questo elenco, ordinarlo in ordine decrescente dal margine. Quindi la valutazione dei costi sarà semplice come

double CalculateHighestMargin(List<ICriteria> orderedCriterias, row)
{
     foreach(var crit in orderedCriterias)
     {
        if(crit.DoesItMatch(row))
            return crit.Price - crit.Cost;
     }
     return 0;
}

Nota: non è necessario memorizzare i valori booleani per le righe in ogni oggetto riga, o il prezzo e il costo di ogni riga (almeno non per i requisiti descritti nella domanda).

    
risposta data 30.03.2017 - 20:53
fonte

Leggi altre domande sui tag