Recopilación de todos los datos en una sola iteración frente al uso de funciones para código legible

8

Digamos que tengo una serie de corredores con los que necesito encontrar el corredor más alto, el corredor más rápido y el corredor más ligero. Parece que la solución más legible sería:

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

... donde cada función itera sobre los corredores y realiza un seguimiento de la altura más grande, la mayor velocidad y el peso más bajo. Sin embargo, iterar sobre la matriz tres veces no parece una muy buena idea. En cambio, sería mejor hacer:

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

Si bien esto no es demasiado ilegible, puede ser complicado cuando hay más lógica para cada pieza de información que se extrae en la iteración.

¿Cuál es el término medio aquí? ¿Cómo puedo usar solo una iteración mientras mantengo el código dividido en unidades lógicas?

mowwwalker
fuente
Qué casualidad. Actualmente estoy pensando exactamente en el mismo problema (solo conmigo son las transformaciones de coordenadas).
sleske

Respuestas:

6

Taskinoor tiene la idea correcta, pero hay una mejor manera de implementarla ... siempre que su idioma admita pasar una referencia de función.

Por ejemplo, así es como lo haría en estilo C #:

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

Esto es trivial de expandir: en lugar de definir tres funciones, puede definir una matriz de Func<Runner, Runner, Runner>funciones anónimas y simplemente ejecutarlas todas. Incluso puede hacerlo con funciones regulares como Runner pickTallest(Runner x, Runner y), aunque luego debe definirlas explícitamente. Sin embargo, la clave es que no tiene que rastrear realmente el valor de cada estadística: solo necesita saber cómo comparar dos Runnersy elegir el que tenga el mejor valor.

Bobson
fuente
2

Esta es una de esas cosas en las que a menudo desea operar con una gran cantidad de datos, en lugar del principio OO de "una sola pieza de datos".

Así que envolvería toda la lista en una clase que en la creación, analiza la lista y calcula lo que quieres. También usaría esta clase para insertar y eliminar de la lista, de modo que la información empaquetada esté siempre actualizada.

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?

}

Eso es. ahora tiene un bloque lógico autónomo que responde sus preguntas por usted con solo una iteración en la creación y cuando elimina objetos.

Matt D
fuente
1

Puede devolver los tres valores de una vez. En pseudocódigo cerca de Scala:

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

Eso te dará una tupla de los corredores que estás buscando. Puedes hacer lo mismo en otros idiomas. Puede que tenga que tomar una biblioteca como Guava o Underscore para hacerlo, y puede que tenga que envolver la tupla en un objeto.

iwein
fuente
0

Cree una clase RunnerStats que calcule las estadísticas en un solo for loop, tal como lo hace en su segundo fragmento de código.

Luego lees las estadísticas en las variables a través de getters. Los captadores solo devuelven los valores ya calculados, no calculan nada.

De esa manera, obtienes lo mejor de ambas soluciones: eficiencia y legibilidad.

runners = getRunners();

RunnerStats rStats = new RunnerStats(runners);

tallest = rStats.getTallets();
fastest = rStats.getFastest();
lightest = rStats.getTallest();
Tulains Córdova
fuente
0

El problema que está describiendo es muy común: tiene un ciclo que produce ciertos datos y desea agregar múltiples "estadísticas" de los datos generales. La implementación sencilla es, como dijiste, tener múltiples variables locales que se actualizan en cada iteración:

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

Esto no tiene una buena separación de preocupaciones, porque tiene la lógica de agregación en línea con la producción de datos. Extraer la lógica de agregación en un método (como se propone en esta respuesta ) es una mejora, pero el aislamiento aún no es bueno: los resultados intermedios y (si es necesario) las variables auxiliares como greatestHeighttodavía tienen que ser variables locales.

Entonces, en mi humilde opinión, la única buena solución es extraer tanto la lógica de agregación como las asignaciones en un método.

¿Cómo se puede hacer esto? Primero refactorizando las variables locales a los campos. Luego, por ejemplo, puede extraer un método updateStats(runner)que actualice los campos tallestRunner/ fastestRunner/ ... y los campos greatestHeight/ greatestSpeed/ ... correspondientes .

¿Pero esto no empeora el aislamiento? Sí, al principio, pero esto se puede solucionar mediante una refactorización de clase de extracto : mueva los campos de estadísticas y el updateStatsmétodo a una nueva clase (por ejemplo, anidada). Entonces, al final, tendrá la siguiente solución legible de un solo paso:

   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; }
      }
 }
oberlies
fuente