Rylander
Rylander

Reputation: 20179

Should I minimize the number of "if" statements or "for" loops?

I have a list of Objects that need to have multiple, conditionally applied, operations applied to each element. Is it more efficient to take a "if-for" approach or a "for-if" approach. To put it another way, should I minimize the number of if statements or the number of for loops? Is there a standard for this?

What would be a good reliable way to determine this?

"If-For" to Approach minimize if statements

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

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

"For-If" Approach to minimize for loops

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

    }
}

Assumptions

Upvotes: 3

Views: 420

Answers (10)

lcguida
lcguida

Reputation: 3847

I think it depends on more things, like: If you find the conditionA, will the loop break? The conditionA and conditionB can coexist? Could I use if-else with them?

Just looking for what you've presented, I think the second aproach is better. You're only looping once and checking twice in the same loop. In my opinion it's also more readable.

Upvotes: 0

Joshua Taylor
Joshua Taylor

Reputation: 85923

First, let's take a look at the complexity of the methods that you've shown so far:

  • The ifForMethod performs k checks, m of which return true. For each of these m, there is an iteration over n objects. The complexity, then, is k+nm.
  • The forIfMethod iterates over n objects and performs k comparisons on each iteration. The complexity, then, is k+n(k-1)=nk.

In both cases, all k conditions have to be evaluated at least once, so the difference here really is in the nm and n(k-1) addends. Asymptotically, m is a just a fraction of k (you said m is approximately .75k), so these are both O(nk), but k+nm < k+n(k-1), so the ifForMethod might be a faster than forIfMethod. The difference in actual run time is going to depend on factors such as the actual time that it takes to iterate over the array, as well as the magnitude of k. You're going to start getting into issues such as memory locality (both for your objects as well as your code).

Here's an approach that you might find interesting, though. Ideally, you'd only want to iterate through the list of objects once, and you wouldn't want to have to check the boolean conditions multiple times. You could abstract away the actions that you're performing in such a way that you could combine them into a single action (and you'd only incorporate those actions that correspond to the conditions that are true), and then perform that compound action on each element in the list. Here's some code that does this.

The idea is that there are Actions, and that you can construct an Action that performs doA and an Action that performs doB. Based on the conditions, you can create a compound action that includes the doA action if the doA condition is true, and the doB action if the doB condition is true. Then you iterate through the objects, and call perform the compound action on each object. Asymptotically, this is a k+nm method, so in theory it performs nicely, but again, the actual performance here will depend on some of those tricky constants, and memory locality issues.

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

Upvotes: 1

user555045
user555045

Reputation: 64933

Using what you called the "If-For" way rather than "For-If" is (perhaps a slightly more general version of) an optimization called loop unswitching. Whether it's actually a good idea depends on several factors, such as (but not limited to)

  • whether that transformation is even allowed (ie conditions have no side effects and doA and doB may be reordered)
  • what you're optimizing for (eg speed, readability, or w/e) though in this case that doesn't really make a difference
  • whether the array fits in cache (iterating over it twice could double the number of cache misses)
  • what the (JIT) compiler makes of it exactly, for example whether the conditions actually compile to branches or not or maybe the compiler does the loop unswitching for you
  • the processor microarchitecture (some µarchs dislike branches inside loops more than others, even if those branches are highly predictable)

Upvotes: 2

Mike Makuch
Mike Makuch

Reputation: 1838

There is no reason to make 2 passes over the list.

Assumptions: predicates are simple booleans, if they have to be evaluated then obviously the cost can change things.

If ((condtionA || conditionB) == true) then both If-for and For-If are both 1 pass. If both predicates can be true then obviously you only want to make one pass.

It doesn't matter what doA and doB since we're assuming they're they same in both If-for and For-If.

If the predicates can change over the course of evaluation then that must be considered.

You're asking a general question so answers are general and vague without more details.

Ok now that you've provided additional info (the list is only 5 elements long, this is part of a build process, the predicates are static booleans) we can see that the bottleneck here is the doA/B functions. Therefore you should only go through the loop once. The static boolean checks are negligible.

Upvotes: 4

Dan Ciborowski - MSFT
Dan Ciborowski - MSFT

Reputation: 7247

The second one is more efficent in terms of how many comparisons you make.

Check condition a, 1 calculation. If true, Object.size calculatons.

Check condition b, 1 calculation. If true, Object.size calculations. Min, 2, Max Object.size * 2

For Method 2, you will always have Object.size * 2 calculations performed.

Consider your "worst case" if both checks are false. Way 1 will only do 2 calculations. Way 2 will perform Object.size * 2. It has nothing to do with your function as in both cases it will always take the same amount of time in both cases.

Even in your "best case" if both checks are true, you are still performing that check N-1 times more for A, and N-1 times more for B.

Best way I think to do it with the fewest calculations.

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

You perform 2 check operations and then only loop through the list once, at max.

Upvotes: 0

Arne Burmeister
Arne Burmeister

Reputation: 20614

It depends! The ifForMethod() solution is best, if there are real cases where neither conditionA nor conditionB is true. If there are cases where conditionA and conditionB are true, solution forIfMethod() is the best but the conditions should be precalculated before entering the loop.

But you can modify forIfMethod() to make it suitable for all cases:

public void forIfMethod() {
  boolean a = conditionA;
  boolean b = conditionB;
  if (a || b) {
    for (Object o : listOfObjects) {
      if (a) {
        doA(o);
      }
      if (b) {
        doB(o);
      }
    }
  }
}

Upvotes: 1

TheGraduateGuy
TheGraduateGuy

Reputation: 1520

lets consider that we are doing same number of operation in both the for loop and inside if .With this standard i will go with the first approach which using if statement before executing for loop just to avoid the number of iteration in for loop.

Also as you are using advance for loop which takes more time to execute the same operation compare to normal for loop.

please correct me if i am wrong.

Upvotes: 1

Seshu Kumar Alluvada
Seshu Kumar Alluvada

Reputation: 562

It Depends on the nature of the business problem your code is trying to solve. If both conditionA AND conditionB are simple Boolean variables but not expressions, then the For-If is going to be better as you are cycling through the list of objects only once.

We are basically comparing which performs better : Enumerating from a list of objects multiple times or evaluating a boolean expression multiple times. If your conditionA/conditionB are very complex Boolean expressions, then your If-For would be a better approach.

Upvotes: 1

dkatzel
dkatzel

Reputation: 31658

Several things:

  1. Is this a premature optimization? Does it really matter if one way is faster than the other, depending on the data it might not be a human noticeable difference.
  2. I would question the design of the software. Why is the same Object have 2 possible conditions? I would recommend breaking the design into multiple objects. Perhaps using subclasses to do the different logic or use the Strategy Pattern. I can't be more specific without a better idea of what you are doing.

Upvotes: -1

stinepike
stinepike

Reputation: 54742

the first one (if-for) sounds good for me.. because for first case there will be a single checking for whole for loop. But in the second cases there will be checking for every loop.

Upvotes: 0

Related Questions