単一の反復ですべてのデータを収集するか、読み取り可能なコードの関数を使用するか


8

たとえば、最も高いランナー、最も速いランナー、最も軽いランナーを見つけるために必要な一連のランナーがあるとします。最も読みやすいソリューションは次のようになります:

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

..各関数はランナーを反復処理し、最高の高さ、最高の速度、最低の重量を追跡します。ただし、配列を3回繰り返し処理することは、あまり良い考えではないようです。代わりにそれを行う方が良いでしょう:

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

これは読みにくくありませんが、反復で抽出される情報の各部分にさらにロジックがあると、面倒になる可能性があります。

ここの中間点は何ですか?コードを論理ユニットに分割したまま、単一の反復のみを使用するにはどうすればよいですか?


なんという偶然なんでしょう。私は現在、まったく同じ問題について考えています(私についてのみ、座標変換です)。
sleske 2013年

回答:


6

Taskinoorは正しいアイデアを持っていますが、実装するより良い方法があります...言語が関数参照の受け渡しをサポートしている場合。

たとえば、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);
}

これは簡単に拡張できます。3つの関数を定義するのではなく、Func<Runner, Runner, Runner>無名関数の配列を定義して、それらすべてを実行できます。のような通常の関数でそれを行うこともできRunner pickTallest(Runner x, Runner y)ますが、それらを明示的に定義する必要があります。ただし、重要な点は、各統計の値を実際に追跡する必要がないことです。2つを比較Runnersし、より良い値の1 つを選択する方法を知る必要があるだけです。


2

これは、「データの1つの断片」というOOの原則ではなく、データのチャンクを操作したいことがよくあるものの1つです。

したがって、リスト全体をクラスにラップして、作成時にリストを解析し、必要なものを計算します。また、このクラスを使用してリストに挿入したり、リストから削除したりして、ラップされた情報が常に最新になるようにします。

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?

}

それでおしまい。これで、作成時およびオブジェクトを削除したときに1回だけ繰り返すだけで、質問に答える自己完結型のロジックブロックが得られます。


1

3つの値すべてを一度に返すことができます。Scalaに近い疑似コード:

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

これで、探しているランナーのタプルが得られます。他の言語でも同じことができます。そのためには、GuavaやUnderscoreなどのライブラリを取得する必要があり、タプルをオブジェクトでラップする必要がある場合があります。


0

for loop2番目のコードスニペットと同じように、単一ので統計を計算するRunnerStatsクラスを作成します。

次に、を介して統計を変数に読み込みますgetters。ゲッターは既に計算された値のみを返し、何も計算しません。

このようにして、効率と読みやすさの両方のソリューションを最大限に活用できます。

runners = getRunners();

RunnerStats rStats = new RunnerStats(runners);

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

0

あなたが説明している問題は非常に一般的です。特定のデータを生成するループがあり、データ全体から複数の「統計」を集計したい場合。簡単な実装は、あなたが言ったように、反復ごとにそれぞれ更新される複数のローカル変数を持つことです:

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

データ生成とインラインで集計ロジックを使用しているため、これは問題を適切に分離するものではありません。(この回答で提案されているように)集約ロジックをメソッドに抽出することは改善ですが、分離はまだ十分ではありません。中間結果と(必要な場合)ヘルパー変数はgreatestHeightローカル変数である必要があります。

したがって、IMHOの唯一の良い解決策は、集約ロジックと割り当ての両方をメソッドに抽出することです。

これはどのように行うことができますか?まずローカル変数をフィールドにリファクタリングします。次に、たとえばupdateStats(runner)tallestRunner/ fastestRunner/ ...フィールドと対応するgreatestHeight/ greatestSpeed/ ...フィールドを更新するメソッドを抽出できます。

しかし、これは分離を悪化させませんか?はい、最初はですが、これは抽出クラスのリファクタリングによって修正できます。統計フィールドとupdateStatsメソッドを新しい(ネストされた)クラスに移動します。したがって、最終的には、次の読み取り可能なシングルパスソリューションが得られます。

   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; }
      }
 }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.