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

8

Digamos que tengo una variedad de corredores con los que necesito encontrar el corredor más alto, el más rápido y el 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 recorre los corredores y realiza un seguimiento de la altura más alta, la velocidad más alta y el peso más bajo. Iterar sobre el arreglo tres veces, sin embargo, no parece ser una muy buena idea. En su lugar, 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 ensuciarse cuando hay más lógica para cada información extraída en la iteración.

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

    
pregunta mowwwalker 21.06.2013 - 07:28

5 respuestas

6

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

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

// 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 para expandir. En lugar de definir tres funciones, puede definir una matriz de Func<Runner, Runner, Runner> de funciones anónimas y ejecutarlas todas. Incluso puedes hacerlo con funciones regulares como Runner pickTallest(Runner x, Runner y) , aunque entonces necesitas definirlas explícitamente. Sin embargo, la clave es que no tiene que seguir realmente el valor de cada estadística, solo necesita saber cómo comparar dos Runners y elegir el que tenga el mejor valor.

    
respondido por el Bobson 21.06.2013 - 08:08
2

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

Así que envolveré toda la lista en una clase que, al crearla, 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 ajustada 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 todo. ahora tiene un bloque de lógica autónomo que responde sus preguntas por usted con solo una iteración en la creación, y cuando elimina objetos.

    
respondido por el Matt D 21.06.2013 - 13:25
1

Puedes devolver los tres valores de una sola vez. En pseudo código cercano a 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.

    
respondido por el iwein 21.06.2013 - 08:07
0

Crea una clase RunnerStats que calcula las estadísticas en un solo for loop , tal como lo haces en tu 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 esta forma, obtiene lo mejor de ambas soluciones: eficiencia y legibilidad.

runners = getRunners();

RunnerStats rStats = new RunnerStats(runners);

tallest = rStats.getTallets();
fastest = rStats.getFastest();
lightest = rStats.getTallest();
    
respondido por el Tulains Córdova 21.06.2013 - 17:34
0

El problema que está describiendo es muy común: tiene un ciclo que produce ciertos datos y desea agregar varias "estadísticas" a partir de los datos generales. La implementación sencilla es, como usted dijo, 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 greatestHeight todavía tienen que ser variables locales.

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

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

¿Pero esto no hace que el aislamiento empeore? Sí, al principio, pero esto se puede solucionar mediante una refactorización de clase de extracción : mueva los campos de estadísticas y el método updateStats a una nueva clase (por ejemplo, anidada). Así que al final tendrás la siguiente solución legible de una sola pasada:

   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; }
      }
 }
    
respondido por el oberlies 25.06.2013 - 17:50

Lea otras preguntas en las etiquetas