3

複数の条件付きで適用される操作を各要素に適用する必要があるオブジェクトのリストがあります。「if-for」アプローチと「for-if」アプローチのどちらを採用する方が効率的ですか。if言い換えれば、ステートメントの数またはforループの数を最小限に抑える必要がありますか? これには基準がありますか?

これを決定するための信頼できる良い方法は何でしょうか?

if ステートメントを最小化するアプローチへの「If-For」

public void ifForMethod() {
    if (conditionA) {
        for (Object o : listOfObjects) {
            doA(o);
        }
    }

    if (conditionB) {
        for (Object o : listOfObjects) {
            doB(o);
        }
    }
}

for ループを最小化する「For-If」アプローチ

public void forIfMethod() {
    for (Object o : listOfObjects) {
        if (conditionA) {
            doA(o);
        }
        if (conditionB) {
            doB(o);
        }

    }
}

仮定

  • 条件は単純なブール値であり、反復中に変更されません。
  • 1 つ以上の条件が true になります。(2つ以上の条件があります)
  • 各条件は、他の条件から独立しています。
  • 内部メソッドは競合したり、相互に作用したりしません。それらが実行される順序は関係ありません。
4

10 に答える 10

4

リストを 2 回パスする必要はありません。

前提: 述語は単純なブール値です。述語を評価する必要がある場合、明らかにコストによって状況が変わる可能性があります。

If ((condtionA || conditionB) == true) の場合、If-for と For-If は両方とも 1 パスです。両方の述語が true である場合、明らかに 1 つのパスのみを作成する必要があります。

If-for と For-If の両方で同じであると想定しているため、doA と doB が何であるかは問題ではありません。

述部が評価の過程で変化する可能性がある場合は、それを考慮する必要があります。

あなたは一般的な質問をしているので、回答は一般的で漠然としており、詳細はありません。

追加情報を提供したので (リストの長さは 5 要素のみです。これはビルド プロセスの一部であり、述語は静的ブール値です)、ここでのボトルネックは doA/B 関数であることがわかります。したがって、ループは 1 回だけ実行する必要があります。静的ブール チェックは無視できます。

于 2013-10-07T17:53:24.577 に答える
2

「For-If」ではなく「If-For」と呼ばれるものを使用することは、(おそらくもう少し一般的なバージョンの)loop unswitchingと呼ばれる最適化です。それが実際に良いアイデアであるかどうかは、次のようないくつかの要因に依存します (ただし、これらに限定されません)。

  • doAその変換が許可されているかどうか (つまり、条件には副作用がなく、順序を変更doBできる)
  • 何を最適化しているか (速度、読みやすさ、w/e など)
  • 配列がキャッシュに収まるかどうか (配列を 2 回繰り返すと、キャッシュ ミスの数が 2 倍になる可能性があります)
  • (JIT)コンパイラが正確に何を作るか、たとえば、条件が実際に分岐にコンパイルされるかどうか、またはコンパイラがループの切り替えを解除するかどうかなど
  • プロセッサのマイクロアーキテクチャ (一部の µarch は、ループ内の分岐を他の分岐よりも嫌います。それらの分岐が非常に予測可能であっても)
于 2013-10-07T22:10:29.740 に答える
1

場合によります!ifForMethod()conditionA も conditionB も true ではない実際のケースがある場合は、この解決策が最適です。conditionA と conditionB が true の場合は、ソリューションforIfMethod()が最適ですが、ループに入る前に条件を事前に計算する必要があります。

forIfMethod()ただし、すべてのケースに適したものに変更できます。

public void forIfMethod() {
  boolean a = conditionA;
  boolean b = conditionB;
  if (a || b) {
    for (Object o : listOfObjects) {
      if (a) {
        doA(o);
      }
      if (b) {
        doB(o);
      }
    }
  }
}
于 2013-10-07T18:02:43.533 に答える
1

for ループと if の両方で同じ回数の操作を行っていると考えてみましょう。

また、通常の for ループと比較して、同じ操作を実行するのにより多くの時間がかかる事前の for ループを使用しているためです。

私が間違っている場合は修正してください。

于 2013-10-07T17:57:42.813 に答える
1

まず、これまでに示したメソッドの複雑さを見てみましょう。

  • k 個のチェックifForMethod を実行し、そのうちm個は true を返します。これらのmごとに、 n 個のオブジェクトに対する反復があります。したがって、複雑さはk+nmです。
  • n 個のオブジェクトをforIfMethod反復し、反復ごとにk回の比較を実行します。したがって、複雑さはk+n(k-1)=nkです。

どちらの場合も、すべてのk条件を少なくとも 1 回評価する必要があるため、ここでの違いは実際にはnmn(k-1) の加数にあります。漸近的に、mはkのほんの一部です( mは約.75kであると言った)、これらは両方とも O( nk ) ですが、k+nm < k+n(k-1)であるため、ifForMethodよりも高速である可能性があります。forIfMethod. 実際の実行時間の違いは、配列の反復処理に実際にかかる時間やkの大きさなどの要因によって異なります。. メモリの局所性 (オブジェクトとコードの両方) などの問題が発生し始めます。

ただし、興味深いと思われるアプローチを次に示します。理想的には、オブジェクトのリストを 1 回だけ反復処理し、ブール条件を複数回チェックする必要はありません。実行中のアクションを抽象化して、それらを単一のアクションに結合できるようにし (真の条件に対応するアクションのみを組み込むことができます)、次にその複合アクションを実行できます。リスト内の各要素。これを行うコードを次に示します。

doAアイデアは、アクションがあり、実行するアクションと実行するアクションを構築できるということですdoB。条件に基づいて、条件が true の場合のアクションと条件が true のdoA場合のアクションを含む複合アクションを作成できます。次に、オブジェクトを繰り返し処理し、各オブジェクトに対して複合アクションを呼び出します。漸近的には、これはk+nmメソッドであるため、理論的にはうまく機能しますが、ここでも、実際のパフォーマンスは、いくつかのトリッキーな定数とメモリの局所性の問題に依存します。doAdoBdoB

import java.util.ArrayList;
import java.util.List;

public class CompoundActionExample {

    /**
     * An action is used to do something to an argument.
     */
    interface Action {
        void act( Object argument );
    }

    /**
     * A compound action is an action that acts on an argument
     * by passing the argument to some other actions.
     */
    static class CompoundAction implements Action {
        /**
         * The list of actions that the compound action will perform.  Additional
         * actions can be added using {@link #add(Action)}, and this list is only
         * accessed through the {@link #act(Object)} method.
         */
        private final List<CompoundActionExample.Action> actions;

        /**
         * Create a compound action with the specified list of actions.
         */
        CompoundAction( final List<CompoundActionExample.Action> actions ) {
            this.actions = actions;
        }

        /**
         * Create a compound action with a fresh list of actions.
         */
        CompoundAction() { 
            this( new ArrayList<CompoundActionExample.Action>() );
        }

        /**
         * Add an action to the compound action.
         */
        public void add( CompoundActionExample.Action action ) {
            actions.add( action );
        }

        /**
         * Act on an argument by passing the argument to each of the 
         * compound action's actions.
         */
        public void act( final Object argument) {
            for ( CompoundActionExample.Action action : actions ) {
                action.act( argument );
            }
        }
    }

    public static void main(String[] args) {
        // Some conditions and a list of objects
        final boolean conditionA = true;
        final boolean conditionB = false;
        final Object[] listOfObjects = { "object1", "object2", "object3" };

        // A compound action that encapsulates all the things you want to do
        final CompoundAction compoundAction = new CompoundAction();

        // If conditionA is true, add an action to the compound action that 
        // will perform doA.  conditionA is evaluated exactly once.
        if ( conditionA ) {
            compoundAction.add( new Action() {
                public void act( final Object argument) {
                    System.out.println( "doA("+argument+")" ); // doA( argument );
                }
            });
        }

        // If conditionB is true, add an action to the compound action that
        // will perform doB. conditionB is evaluted exactly once.
        if ( conditionB )  {
            compoundAction.add( new Action() {
                public void act(Object argument) {
                    System.out.println( "doB("+argument+")" ); // doB( argument );
                }
            });
        }

        // For each object, apply the compound action
        for ( final Object o : listOfObjects ) {
            compoundAction.act( o );
        }
    }
}
于 2013-10-07T21:39:34.237 に答える
0

比較回数に関しては、2 番目の方法の方が効率的です。

条件a、1の計算を確認してください。true の場合、Object.size の計算。

条件 b、1 の計算を確認します。true の場合、Object.size の計算。最小、2、最大 Object.size * 2

方法 2 では、常に Object.size * 2 の計算が実行されます。

両方のチェックが false の場合は、「最悪のケース」を考慮してください。方法 1 は 2 つの計算のみを行います。方法 2 は Object.size * 2 を実行します。どちらの場合も常に同じ時間がかかるため、関数とは関係ありません。

両方のチェックが真の場合の「最良のケース」でも、A の場合は N-1 倍、B の場合は N-1 倍のチェックを実行しています。

私が考える最良の方法は、最も少ない計算でそれを行うことです。

public void ifForMethod() {
    if (conditionA) {
        if(conditionB){
            for (Object o : listOfObjects) {
                doA(o);
                doB(o);
            }
        else{
            for (Object o : listOfObjects) {
                doA(o);
            }    
        }
    }
    else if (conditionB) {
        for (Object o : listOfObjects) {
            doB(o);
        }
    }
}

2 つのチェック操作を実行してから、最大でリストを 1 回だけループします。

于 2013-10-07T21:17:04.987 に答える
0

最初のもの(if-for)は私にとっては良さそうです..最初のケースでは、forループ全体を1回チェックするためです。しかし、2 番目のケースでは、ループごとにチェックが行われます。

于 2013-10-07T17:42:00.287 に答える
0

私はそれがより多くのものに依存していると思います: conditionA を見つけた場合、ループは壊れますか? conditionA と conditionB は共存できますか? それらで if-else を使用できますか?

あなたが提示したものを探しているだけで、2番目のアプローチの方が優れていると思います。ループは 1 回だけで、同じループで 2 回チェックしています。私の意見では、それはまたより読みやすいです。

于 2013-10-07T17:48:05.783 に答える
-1

いくつかのこと:

  1. これは時期尚早の最適化ですか? ある方法が他の方法よりも速いかどうかは本当に重要ですか? データによっては、人間の目に見える違いではないかもしれません.
  2. ソフトウェアの設計に疑問を感じます。同じオブジェクトに 2 つの条件があるのはなぜですか? デザインを複数のオブジェクトに分割することをお勧めします。おそらく、サブクラスを使用して別のロジックを実行したり、戦略パターンを使用したりします。あなたが何をしているのかについてのより良い考えなしに、私はこれ以上具体的に言うことはできません.
于 2013-10-07T17:42:14.023 に答える