Reunindo todos os dados em iteração única vs usando funções para código legível

8

Digamos que eu tenha uma variedade de corredores com os quais preciso encontrar o corredor mais alto, o mais rápido e o mais leve. Parece que a solução mais legível seria:

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

..onde cada função interage com os corredores e monitora a maior altura, maior velocidade e menor peso. Iterar a matriz três vezes, no entanto, não parece uma boa ideia. Em vez disso, seria melhor fazer:

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

Embora isso não seja muito ilegível, pode ficar confuso quando houver mais lógica para cada parte da informação extraída na iteração.

Qual é o meio termo aqui? Como posso usar apenas uma única iteração, mantendo o código dividido em unidades lógicas?

mowwwalker
fonte
Que coincidência. Atualmente, estou pensando exatamente no mesmo problema (somente comigo são transformações de coordenadas).
sleske

Respostas:

6

O Taskinoor tem a idéia certa, mas há uma maneira melhor de implementá-lo ... desde que seu idioma suporte a passagem de uma referência de função.

Por exemplo, aqui está como eu faria isso no 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);
}

Isso é trivial para expandir - em vez de definir três funções, você pode definir uma matriz de Func<Runner, Runner, Runner>funções anônimas e apenas executá-las. Você pode até fazê-lo com funções regulares como Runner pickTallest(Runner x, Runner y), embora precise defini-las explicitamente. A chave, no entanto, é que você não precisa rastrear o valor de cada estatística - você só precisa saber como comparar duas e escolher a que tiver o melhor valor.Runners

Bobson
fonte
2

Essa é uma daquelas coisas em que muitas vezes você deseja operar com uma porção de dados, em vez do princípio OO de "uma única peça de dados".

Então, eu agruparia a lista inteira em uma classe que, na criação, analisa a lista e calcula o que você deseja. Você também usaria essa classe para inserir e remover da lista, para que as informações agrupadas estejam sempre atualizadas.

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?

}

É isso aí. agora você tem um bloco de lógica independente que responde às suas perguntas com apenas uma iteração na criação e quando você remove objetos.

Matt D
fonte
1

Você pode retornar todos os três valores de uma só vez. No pseudo-código próximo ao Scala:

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

Isso fornecerá uma tupla dos corredores que você está procurando. Você pode fazer o mesmo em outros idiomas. Pode ser necessário pegar uma biblioteca como Guava ou Underscore para fazê-lo e envolver a tupla em um objeto.

iwein
fonte
0

Crie uma classe RunnerStats que calcule as estatísticas em uma única for loop, assim como você faz no seu segundo trecho de código.

Então você lê as estatísticas nas variáveis ​​via getters. Os getters retornam apenas os valores já calculados, eles não calculam nada.

Dessa forma, você obtém o melhor das duas soluções: eficiência e legibilidade.

runners = getRunners();

RunnerStats rStats = new RunnerStats(runners);

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

O problema que você está descrevendo é muito comum: você tem um loop que produz determinados dados e deseja agregar várias "estatísticas" dos dados gerais. A implementação direta é, como você disse, ter várias variáveis ​​locais que são atualizadas a cada iteração:

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

Isso não tem uma boa separação de preocupações, porque você tem a lógica de agregação alinhada com a produção dos dados. Extrair a lógica de agregação em um método (como proposto nesta resposta ) é uma melhoria, mas o isolamento ainda não é bom: os resultados intermediários e (se necessário) variáveis ​​auxiliares como greatestHeightainda precisam ser variáveis ​​locais.

Portanto, a IMHO é a única boa solução para extrair a lógica de agregação e as atribuições em um método.

Como isso pode ser feito? Refatorando primeiro as variáveis ​​locais em campos. Em seguida, você pode, por exemplo, extrair um método updateStats(runner)que atualize os campos tallestRunner/ fastestRunner/ ... e os campos greatestHeight/ greatestSpeed/ ... correspondentes .

Mas isso não piora o isolamento? Sim, a princípio, mas isso pode ser corrigido por uma refatoração de classe de extração : Mova os campos estatísticos e o updateStatsmétodo para uma nova classe (por exemplo, aninhada). Portanto, no final, você terá a seguinte solução legível e de passagem única:

   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
fonte