Raccolta di tutti i dati in un'unica iterazione rispetto all'utilizzo di funzioni per codice leggibile

8

Dire che ho una serie di corridori con cui ho bisogno di trovare il corridore più alto, il corridore più veloce e il corridore più leggero. Sembra che la soluzione più leggibile sia:

runners = getRunners();
tallestRunner = getTallestRunner(runners);
fastestRunner = getFastestRunner(runners);
lightestRunner = getLightestRunner(runners);

.. dove ogni funzione scorre sui corridori e tiene traccia dell'altezza maggiore, della massima velocità e del peso più basso. L'iterazione sull'array tre volte, tuttavia, non sembra un'ottima idea. Sarebbe invece meglio fare:

int greatestHeght, greatestSpeed, leastWeight;
Runner tallestRunner, fastestRunner, lightestRunner;
for(runner in runners){
    if(runner.height > greatestHeight) { greatestHeight = runner.height; tallestRunner = runner; }
    if(runner.speed > ...
}

Anche se questo non è troppo illeggibile, può diventare complicato quando c'è più logica per ogni informazione che viene estratta nell'iterazione.

Qual è la via di mezzo qui? Come posso utilizzare solo una singola iterazione mantenendo il codice diviso in unità logiche?

    
posta mowwwalker 21.06.2013 - 07:28
fonte

5 risposte

6

Taskinoor ha l'idea giusta, ma c'è un modo migliore per implementarlo ... purché la tua lingua supporti il passaggio di una funzione di riferimento.

Ad esempio, ecco come lo farei in C # -ish style:

// Define three anonymous functions which take two Runners, compare them, and return one.
Func<Runner, Runner, Runner> tallest = (x,y) => x.height > y.height ? x : y;
Func<Runner, Runner, Runner> fastest = (x,y) => x.speed > y.speed ? x : y;
Func<Runner, Runner, Runner> lightest = (x,y) => x.weight < y.weight ? x : y;

// Default everything to the first runner, to keep things simple 
Runner tallestRunner = fastestRunner = lightestRunner = runners.First();

// Loop
foreach(runner in runners){
    tallestRunner = tallest(tallestRunner, runner);
    fastestRunner = fastest(fastestRunner, runner);
    lightestRunner = lightest(lightestRunner, runner);
}

Questo è banale da espandere - piuttosto che definire tre funzioni, puoi definire una matrice di Func<Runner, Runner, Runner> funzioni anonime, e semplicemente eseguirle tutte. Puoi persino farlo con funzioni regolari come Runner pickTallest(Runner x, Runner y) , sebbene sia necessario definirli esplicitamente. La chiave, tuttavia, è che non devi tracciare il valore per ogni stat - devi solo sapere come confrontare due Runners e scegliere quello con il valore migliore.

    
risposta data 21.06.2013 - 08:08
fonte
2

Questa è una di quelle cose in cui molto spesso desideri operare su una porzione di dati, piuttosto che il principio OO di "un singolo pezzo di dati".

Quindi racchiuderei l'intera lista in una classe che alla creazione, analizza la lista e calcola ciò che vuoi. Dovresti utilizzare questa classe anche per inserirla e rimuoverla dall'elenco, in modo che le informazioni incapsulate siano sempre aggiornate.

class Runners
{
    public Runners( RunnerList inputRunners)
    {
        runners = inputRunners;
        Recalculate();
    }

    private Recalculate()
    {  
       foreach( Runner runner in runners )
       {
           // comparisons here!
       }
    }

    public Insert(Runner newRunner)
    {
        int index = runners.Add(newRunner);
        if( newRunner.height > runners[highestIndex].height)
        {
            highestIndex = index;
        }
        // and so on.
    }

    public Remove(Runner delRunner)
    {
        runners.Remove(delRunner);
        Recalculate();
    }

    // accessors
    Runner GetHighest() { return runners[highestIndex]; }
    Runner GetFastest() { return runners[fastestIndex]; }
    Runner GetLightest() { return runners[lightestIndex]; }

    RunnerList runners; // list of runners we manage
    int highestIndex;   // index of element in list which is highest.
    int fastestIndex;   // index of element in list which is fastest
    int lightestIndex;  // you get the idea right?

}

Ecco. ora hai un blocco di logica autonomo che risponde alle tue domande per te con una sola iterazione sulla creazione e quando rimuovi gli oggetti.

    
risposta data 21.06.2013 - 13:25
fonte
1

Puoi restituire tutti e tre i valori in un colpo solo. In pseudo codice vicino a Scala:

val (fastest, tallest, lightest) = runners
  .foldLeft(...)(((tallestSoFar, fastestSoFar, lightestSoFar),runner) =>
   (tallest(runner, tallestSoFar), 
    fastest(runner, fastestSoFar), 
    lightest(runner, lightestSoFar))

Questo ti darà una tupla dei corridori che stai cercando. Puoi fare lo stesso in altre lingue. Potrebbe essere necessario prendere una libreria come Guava o Underscore per farlo, e potrebbe essere necessario avvolgere la tupla in un oggetto.

    
risposta data 21.06.2013 - 08:07
fonte
0

Crea una classe RunnerStats che calcola le statistiche in un singolo for loop , proprio come fai nel tuo secondo snippet di codice.

Quindi leggi le statistiche nelle variabili tramite getters . I getter restituiscono solo i valori già calcolati, non calcolano nulla.

In questo modo ottieni il meglio da entrambe le soluzioni: efficienza e leggibilità.

runners = getRunners();

RunnerStats rStats = new RunnerStats(runners);

tallest = rStats.getTallets();
fastest = rStats.getFastest();
lightest = rStats.getTallest();
    
risposta data 21.06.2013 - 17:34
fonte
0

Il problema che stai descrivendo è molto comune: hai un ciclo che produce determinati dati e vuoi aggregare più "statistiche" dai dati generali. L'implementazione semplice è, come hai detto, per avere più variabili locali che vengono aggiornate ciascuna in ogni iterazione:

   int greatestHeight, greatestSpeed, leastWeight;
   Runner tallestRunner, fastestRunner, lightestRunner;
   for(...){
      runner = ... // the runner comes from somewhere, not necessarily a list
      if(runner.height > greatestHeight) { greatestHeight = runner.height; tallestRunner = runner; }
      if(runner.speed > ...
   }

Questo non ha una buona separazione delle preoccupazioni, perché hai la logica di aggregazione in linea con la produzione dei dati. L'estrazione della logica di aggregazione in un metodo (come proposto in questa risposta ) è un miglioramento, ma l'isolamento non è ancora buono: i risultati intermedi e (se necessario) variabili helper come greatestHeight devono ancora essere variabili locali.

Quindi IMHO l'unica buona soluzione è estrarre sia la logica di aggregazione sia i compiti in un metodo.

Come si può fare? Riconoscendo innanzitutto le variabili locali ai campi. Quindi, puoi ad es. estrai un metodo updateStats(runner) che aggiorna i campi tallestRunner / fastestRunner / ... e i corrispondenti campi greatestHeight / greatestSpeed / ....

Ma questo non rende l'isolamento peggiore? Sì, all'inizio, ma questo può essere risolto da un refactoring estrai classe : sposta i campi delle statistiche e il metodo updateStats in una nuova classe (ad esempio nidificata). Quindi alla fine avremo la seguente soluzione a singolo passaggio leggibile:

   RunnerStats stats = new RunnerStats();
   for(...){
       runner = ...
       stats.updateStats(runner);
    }
    ... = stats.tallestRunner;
 }

 static class RunnerStats{
      int greatestHeight, greatestSpeed, leastWeight = Integer.MAX_VALUE;
      Runner tallestRunner, fastestRunner, lightestRunner;

      void updateStats(Runner runner){
          updateTallest(runner);
          update...
      }

      void updateTallest(Runner runner){
           if(runner.height > greatestHeight) { greatestHeight = runner.height; tallestRunner = runner; }
      }
 }
    
risposta data 25.06.2013 - 17:50
fonte

Leggi altre domande sui tag