Sammeln aller Daten in einer einzigen Iteration im Vergleich zur Verwendung von Funktionen für lesbaren Code


8

Angenommen, ich habe eine Reihe von Läufern, mit denen ich den größten Läufer, den schnellsten Läufer und den leichtesten Läufer finden muss. Die am besten lesbare Lösung scheint zu sein:

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

..wobei jede Funktion über die Läufer iteriert und die größte Höhe, die größte Geschwindigkeit und das niedrigste Gewicht verfolgt. Dreimal über das Array zu iterieren, scheint jedoch keine sehr gute Idee zu sein. Es wäre stattdessen besser zu tun:

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

Dies ist zwar nicht zu unlesbar, kann jedoch unübersichtlich werden, wenn für jede in der Iteration extrahierte Information mehr Logik vorhanden ist.

Was ist der Mittelweg hier? Wie kann ich nur eine einzige Iteration verwenden, während der Code in logische Einheiten unterteilt bleibt?


Was für ein Zufall. Ich denke gerade über genau das gleiche Problem nach (nur bei mir sind es Koordinatentransformationen).
Sleske

Antworten:


6

Taskinoor hat die richtige Idee, aber es gibt eine bessere Möglichkeit, sie umzusetzen ... vorausgesetzt, Ihre Sprache unterstützt die Weitergabe einer Funktionsreferenz.

Hier ist zum Beispiel, wie ich es im C # -ischen Stil machen würde:

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

Das Erweitern ist trivial. Anstatt drei Funktionen zu definieren, können Sie ein Array Func<Runner, Runner, Runner>anonymer Funktionen definieren und einfach alle ausführen. Sie können dies sogar mit regulären Funktionen wie tun Runner pickTallest(Runner x, Runner y), obwohl Sie diese dann explizit definieren müssen. Der Schlüssel ist jedoch, dass Sie nicht den Wert für jede Statistik verfolgen müssen - Sie müssen nur wissen, wie man zwei vergleicht Runnersund diejenige mit dem besseren Wert auswählt.


2

Dies ist eines der Dinge, bei denen Sie häufig einen Datenblock bearbeiten möchten und nicht das OO-Prinzip "eines einzelnen Datenelements".

Also würde ich die gesamte Liste in eine Klasse einschließen, die bei der Erstellung die Liste analysiert und berechnet, was Sie wollen. Sie können diese Klasse auch zum Einfügen und Entfernen aus der Liste verwenden, damit die umschlossenen Informationen immer auf dem neuesten Stand sind.

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?

}

Das ist es. Sie haben jetzt einen in sich geschlossenen Logikblock, der Ihre Fragen mit nur einer einzigen Iteration bei der Erstellung und beim Entfernen von Objekten beantwortet.


1

Sie können alle drei Werte auf einmal zurückgeben. Im Pseudocode in der Nähe von Scala:

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

Das gibt Ihnen ein Tupel der Läufer, die Sie suchen. Sie können dasselbe in anderen Sprachen tun. Möglicherweise müssen Sie eine Bibliothek wie Guava oder Underscore herunterladen, um dies zu tun, und Sie müssen das Tupel möglicherweise in ein Objekt einschließen.


0

Erstellen Sie eine RunnerStats-Klasse, die die Statistiken in einem einzigen berechnet for loop, genau wie in Ihrem zweiten Code-Snippet.

Dann lesen Sie die Statistiken in die Variablen über getters. Die Getter geben nur die bereits berechneten Werte zurück, sie berechnen nichts.

Auf diese Weise erhalten Sie das Beste aus beiden Lösungen: Effizienz und Lesbarkeit.

runners = getRunners();

RunnerStats rStats = new RunnerStats(runners);

tallest = rStats.getTallets();
fastest = rStats.getFastest();
lightest = rStats.getTallest();

0

Das Problem, das Sie beschreiben, ist sehr häufig: Sie haben eine Schleife, die bestimmte Daten erzeugt, und Sie möchten mehrere "Statistiken" aus den Gesamtdaten aggregieren. Die einfache Implementierung besteht, wie Sie sagten, darin, mehrere lokale Variablen zu haben, die jeweils in jeder Iteration aktualisiert werden:

   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 > ...
   }

Dies hat keine gute Trennung der Bedenken, da die Aggregationslogik mit der Datenproduktion übereinstimmt. Das Extrahieren der Aggregationslogik in eine Methode (wie in dieser Antwort vorgeschlagen ) ist eine Verbesserung, aber die Isolation ist immer noch nicht gut: Die Zwischenergebnisse und (falls erforderlich) Hilfsvariablen wie müssen greatestHeightimmer noch lokale Variablen sein.

IMHO ist die einzig gute Lösung, sowohl die Aggregationslogik als auch die Zuweisungen in eine Methode zu extrahieren .

Wie kann das gemacht werden? Indem Sie zuerst die lokalen Variablen in Felder umgestalten. Dann können Sie zB eine Methode extrahieren, updateStats(runner)die die Felder tallestRunner/ fastestRunner/ ... und die entsprechenden Felder greatestHeight/ greatestSpeed/ ... aktualisiert .

Aber macht das die Isolation nicht noch schlimmer? Ja, zunächst, aber dies kann durch ein Refactoring der Extraktklasse behoben werden : Verschieben Sie die Statistikfelder und die updateStatsMethode in eine neue (z. B. verschachtelte) Klasse. Am Ende haben Sie also die folgende lesbare Single-Pass-Lösung:

   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; }
      }
 }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.