3

NOTE: I'm under the impression that you want to avoid logic statements inside loops. This I believe was stated partly because of how the compiler can optimize any loop where iterations behave predictable. While I'm almost certain I've heard this before and for a long time I've thought of this as a convention. Sadly I couldn't find any good references. If this is true however there are some conflicting cases due to the "DRY" principle (Don't repeat yourself).

PRETEXT: Assuming that you have a rather sizable data set, say a multi-dimensional array as I used in this example. Furthermore assume you need to traverse every entry and do some operation on all or some of the elements, and that you need to be able to choose that one or another operation is to be performed. This calls for making two methods where 90%-99% of the code is identical between the two, while only an operator or a method call is different. If this had been C++ I would have wanted to provide a function pointer to a loop function, though I do not know if this too would be preferred avoided.

QUESTION: Would it be preferred to use logic statements and have only one loop or rather two almost identical methods?

EXAMPLE: I've provided some example to show how redundant the "twin" method solution looks:

// This method is provided for completeness of the example
// and to provide some clue as to what boolean parameter and logic statement 
// I could alternatively have implemented within the loop method instead of external to it.
public int[][] addOrSubtractArrays(int[][] a, int[][] b, boolean useAdd){
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length)
        return null;
    return useAdd ? add(a, b) : subtract(a, b);
}

private int[][] add(int[][] a, int[][] b){
    int h = a.length;
    int w = a[0].length;
    int[][] c = new int[h][w];
    for(int y = 0; y < h; y++){
        for(int x = 0; x < w; x++){
            c[y][x] = a[y][x] + b[y][x];
        }
    }
    return c;
}

private int[][] subtract(int[][] a, int[][] b){
    int h = a.length;
    int w = a[0].length;
    int[][] c = new int[h][w];
    for(int y = 0; y < h; y++){
        for(int x = 0; x < w; x++){
            c[y][x] = a[y][x] - b[y][x];
        }
    }
    return c;
}

EXAMPLE 2: The (obvious?) alternative

private int[][] addOrSubtract(int[][] a, int[][] b, boolean useAdd){
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length)
        return null;
    int h = a.length;
    int w = a[0].length;
    int[][] c = new int[h][w];
    for(int y = 0; y < h; y++){
        for(int x = 0; x < w; x++){
            if(useAdd)
                c[y][x] = a[y][x] + b[y][x];
            else
                c[y][x] = a[y][x] - b[y][x];
        }
    }
    return c;
}

I am overly tempted to make some common method holding the entire loop structure to avoid (almost) duplicate code. However if "what I've heard" has some reasonable context, this may to my knowledge be best avoided.

Chexxor
  • 406
  • 6
  • 17
  • Cant answer on your question, but I would use the the first example with two functions. You might repeat a bit of code, but it later on makes your code easier to read when using those functions. No need to look up what the boolean stands for. – Mats391 Nov 23 '16 at 07:23

3 Answers3

2

As you have said, if this were C++, you would pass a function pointer. Well, there's something similar if you're using Java 8 - BiFunction<Integer, Integer, Integer>

Your single method will look like this:

private int[][] addOrSubtract(int[][] a, int[][] b, boolean useAdd){
    BiFunction<Integer, Integer, Integer> func = useAdd ? 
        ((a, b) -> a + b) : ((a, b) -> a - b);
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length)
        return null;
    int h = a.length;
    int w = a[0].length;
    int[][] c = new int[h][w];
    for(int y = 0; y < h; y++){
        for(int x = 0; x < w; x++){
            c[y][x] = func.apply(a[y][x], b[y][x]);
        }
    }
    return c;
}

If you're not using Java 8, then I guess you can just use either of your two solutions. I don't think there's anything wrong with them. And remember, premature optimization is the root of all evil!

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • hey sweeper, this could be irrelevant to the answer, but can you please explain `premature optimization` ? i have googled it, it's a a quote, but still can't get what it means exactly :) thanks! – Yazan Nov 23 '16 at 07:41
  • 1
    It basically means that don't conciously do optimization before everything else is done. I am just warning the OP that do your project _first_, worry about this later. @Yazan – Sweeper Nov 23 '16 at 07:43
  • okkki i got it :) , don't get overwhelmed by optimization while your project is still not complete, looks good to me :) – Yazan Nov 23 '16 at 07:46
  • 1
    @Yazan The idea behind it is to profile your application before spending time on optimization. A developer may waste hours optimizing something that makes negligable difference (results are hardly noticeable), when there could be many other areas of your application that may *need* to be optimized or could better benefit from optimization. "Premature" optimization is optimizing something before knowing whether it needs/could benefit from optimization. – Vince Nov 23 '16 at 08:12
  • 1
    That's a fair point, but I would still think that training to automatically use more optimized alternatives as a habit would make future code development better optimized without taking up extra time. That's the reason I think these kinds of problems and questions are important. – Chexxor Nov 23 '16 at 08:23
  • @Chexxor Writing high performance software without profiling differs from optimizing code you *think* will benefit from it. Understanding your enviornment (APIs, VM revisions), will allow you to avoid bottlenecks. Learning about your enviornment requires you to first be exposed to the statistics, which you can find by profiling. While profiling, you may realize there are other areas that could benefit a lot more from optimization, which is why its best to avoid wasting time trying to optimize prematurely. Feel free to create a chat if you're still not convinced ;) – Vince Nov 23 '16 at 09:02
  • @VinceEmigh If only I could find the chat button when the webpage has not yet suggested I use it. You're missing my point. You're talking about spending time during development on optimization, I am not. I'm talking about which alternative to choose as my best practice, to choose as a habit, not what suits me best right now. This by no means steals any of my time from coding. I'm not even doing any projects right now. Basically I want to know the best solution because I'm curious. And also, knowing this would on the contrary make me spend less time thinking about in the future, not more. – Chexxor Nov 23 '16 at 09:21
  • @Chexxor It's not just during development, it's during any time you invest in optimizing. Your curiosity can be cured by profiling, checking to see the actual results. Chances are whoever answers this question from a performance perspective will profile/benchmark the code (or they have benchmarked something similar before and are speaking from experience). It's less about saving time, more about not wasting it through assumptions, since the facts can easily be gathered. [I have created a chat](http://chat.stackoverflow.com/rooms/128878/premature-optimization-evil) if you're interested – Vince Nov 23 '16 at 21:17
1

this could be out of your question's scope, as i don't have an answer if logic in loop is good or not, but it's always good to consider other designs. like this one, it does not have a repeated code, and it does not have a logic in the for loop

public int[][] addOrSubtractArrays(int[][] a, int[][] b, boolean useAdd){
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length)
        return null;
    int h = a.length;
    int w = a[0].length;
    int[][] c = new int[h][w];
    for(int y = 0; y < h; y++){
        for(int x = 0; x < w; x++){
            c[y][x] = getResult(a[y][x] , b[y][x], useAdd);
        }
    }

    return c;
}

private int getResult(int elementA, int elementB, boolean useAdd){
    return useAdd ? (elementA+elementB) : (elementA-elementB);
}
Yazan
  • 6,074
  • 1
  • 19
  • 33
  • So by outsourcing the logic part to a separate method, would that still make the compiler able to optimize the loops to the same extent? Or would a function call inside a loop make for a less optimized code? That question is some of the essence in my problem. – Chexxor Nov 23 '16 at 08:19
  • 1
    @Chexxor i don't think this is a straight-forward,that can be concluded based on compiler's behavior in a certain case, this case is very simple (add or sub) what if the for loop body have more stuff? more complex logic ... something you can do is putting all the above methods in a class, compile it, use a tool for de-compile the `.class` file and see if the code was changed by the compiler, if NOT, then it's either very efficient, need~but~can't be optimized or simply it does not make a noticeable difference, (and again: could it make a difference with a different logic in the loop?) – Yazan Nov 23 '16 at 08:58
  • @Yazan I believe he's talking about runtime compilation optimizations (from the [JIT compiler](http://stackoverflow.com/questions/95635/what-does-a-just-in-time-jit-compiler-do)). Reading the bytecode will not determine runtime optimizations. Keep in mind there are 2 compilers at play: `javac` which compiles Java to Bytecode, and `jit` which compiles bytecode to native code (or just interprets the bytecode). – Vince Nov 24 '16 at 18:17
1

addOrSubtract is bad. What about other forms of arithmetic, such as multiply? You could expose a multiplyOrDivide, but what if a time comes where the choices should be addOrMultiply? Doesn't scale well.

What you should have is a method that allows the client to specify what operation to perform:

public int[][] calculate(int[][] first, int[][] second, Operation operation) {
    if(firstArray == null || secondArray == null || firstArray.length != secondArray.length || firstArray.length < 1 || firstArray[0].length != secondArray.length)
        throw new IllegalArgumentException("Arrays can't be null and must be of equal length.");

    int height = firstArray.length;
    int width = firstArray[0].length;
    int[][] result = new int[height][width];
    for(int y = 0; y < height; y++){
        for(int x = 0; x < width; x++){
            result[y][x] = operation.performOn(firstArray[y][x], secondArray[y][x]);
        }
    }
}

You can now add new operations as needed:

enum Operation {
    ADD {
        @Override
        public void performOn(int firstValue, int secondValue) {
            return firstValue + secondValue;
        }
    },
    SUBTRACT {
        //...
    };

    public abstract int performOn(int firstValue, int secondValue);
}

If you feel overriding in this fashion makes scaling too verbose, you can make use of the strategy pattern by delegating the logic to a callback function:

enum Operation { //could/should implement IOperation
    ADD((a, b) -> a + b),
    SUBTRACT((a, b) -> a - b);

    private IOperation operation;

    Operation(IOperation operation) {
        this.operation = operation;
    }

    public final int performOn(int firstValue, int secondValue) {
        return operation.performOn(firstValue, secondValue);
    }
}

interface IOperation {
    int performOn(int firstValue, int secondValue);
}

A client can now use your function as demonstrated below:

calculate(firstArray, secondArray, Operation.ADD);

The reason I chose to create a new functional interface rather than use BiFunction is to avoid autoboxing. Performance seems to be a worry of yours, and autoboxing can dramatically affect performance, especially if you'll be performing this intensely. Whether it be from arrays having large sizes, or from being required to call addOrSubtract continuously within a small timeframe, it's best to avoid the pitfall.

The IllegalArgumentException allows the program to "blow up" with a descriptive message. You returned null, which means whoever uses this method needs to perform a null-check (clutter-some, code smell, the billion dollar mistake) or they might encounter a NullPointerException which does not have a descriptive message.

Vince
  • 14,470
  • 7
  • 39
  • 84
  • I've never used IOperation, but the syntax it's using, is it/does it relate to lambda expressions? And the return null was just to avoid overcomplicating the example, not something I would've done in actual code. – Chexxor Nov 24 '16 at 03:07
  • 1
    Yes, it's a functional interface, which allows you to create lambda expressions anywhere that interface is specified. I defined `IOperation`, so you won't find it in the JDK, but you can always define your own interface, or use `BiFunction` if you don't want to create your own. Using `BiFunction` could be quite expensive in your situation though, due to the autoboxing. Then again, that's pre-optimization, and if you're really worried about the effects of using `BiFunction` you should benchmark the differences. – Vince Nov 24 '16 at 17:26