116

I have a situation where the return statement nested in two for loops will always be reached, theoretically.

The compiler disagrees and requires a return statement outside of the for loop. I'd like to know an elegant way to optimize this method that's beyond my current understanding, and none of my attempted implementations of break seem to work.

Attached is a method from an assignment that generates random integers and returns the iterations cycled through until a second random integer is found, generated within a range passed into the method as an int parameter.

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
    }
    return 0; // Never reached
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oliver Benning
  • 1,281
  • 2
  • 8
  • 14
  • 1
    If you want to break out of both loops, you can put a label before the first `for` loop and then do a `break labelName;` – David Choweller Feb 12 '17 at 05:12
  • The label would look like this: `labelName: for (int count = 1; ...` – David Choweller Feb 12 '17 at 05:13
  • 9
    If the last item is never reached, then you can use a `while(true)` rather than an indexed loop. This tells the compiler that the loop will never return. – Boris the Spider Feb 12 '17 at 09:33
  • 101
    call the function with range as 0 (or any other number less than 1) (`oneRun(0)`) and you see that you quickly reach your unreachable `return` – mcfedr Feb 12 '17 at 12:42
  • 55
    The return is reached when the supplied range is negative. You also have 0 validation for the input range, so you are not currently catching it in any other way. – HopefullyHelpful Feb 12 '17 at 14:54
  • 1
    Why not omit the termination condition of the loop? – Reinstate Monica Feb 13 '17 at 02:41
  • 4
    @HopefullyHelpful That is the real answer, of course. It's not a useless return at all! – Mr Lister Feb 13 '17 at 07:19
  • I believe that the code is finding the first count where the generator has returned a repeated value. If there *is* no repeated value, then you will hit the return. That is likely if range is less than about 6500, and has a non-zero chance of happening for any value of range. – Martin Bonner supports Monica Feb 13 '17 at 10:45
  • The accepted answer is the correct one, but I think it's important to realize that removing a line of code is not an 'optimization'. This is especially true if you never expect it to be reached. – JimmyJames Feb 13 '17 at 15:27
  • @MartinBonner but since all randomly selected values are in the range [0, n), if you pick n+1 values, you *must* selected at least one twice. – njzk2 Feb 13 '17 at 16:27
  • @njzk2. Ah! That limit on the range is what I missed. Yes. (But you can understand why the Java compiler wasn't able to make that deduction!) – Martin Bonner supports Monica Feb 13 '17 at 16:30
  • 4
    @HopefullyHelpful no, because the `nextInt` throws an exception for `range < 0` The only case where the return is reached is when `range == 0` – njzk2 Feb 13 '17 at 17:51
  • @mcfedr nope - Random.nextInt throws IllegalArgumentException if passed a value <= 0 – Pete Kirkham Feb 15 '17 at 10:29
  • 1
    @PeteKirkham but again, the compiler doesn't understand this, so it cannot make the assumption that the 'useless' return is never reached. – mcfedr Feb 15 '17 at 10:38

10 Answers10

344

The compiler's heuristics will never let you omit the last return. If you're sure it'll never be reached, I'd replace it with a throw to make the situation clear.

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range; count++) {
        ...
    }

    throw new AssertionError("unreachable code reached");
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 135
    Not only readability, but makes sure you find out if there's something wrong with your code. It's completely plausible the generator could have a bug. – JollyJoker Feb 12 '17 at 12:05
  • 6
    If you are ABSOLUTELY sure that your code can never get to that "unreachable code" then create some unit tests for all the edge cases that could arise (range is 0, range is -1, range is min/max int). It may be a private method now, but the next developer might not keep it that way. How you handle unexpected values (throwing an exception, returning an error value, doing nothing) depends on how you are going to use the method. In my experience, you usually just want to log an error and return. – Rick Ryker Feb 13 '17 at 18:18
  • 22
    Maybe that should be `throw new AssertionError("\"unreachable\" code reached");` – Bohemian Feb 13 '17 at 22:44
  • @RickRyker This was for a university assignment that is very one and done, how would I write this method assuming it was part of a sophisticated project? – Oliver Benning Feb 14 '17 at 01:24
  • 8
    I would write exactly what I put in my answer in a production program. – John Kugelman Feb 14 '17 at 01:29
  • 1
    For the given example there is a better way to deal with than to simply add a `throw` that will never be reached. Way too often people just want to quickly apply "one solution to rule them all" without thinking too much about the underlying problem. But programming is much more than just coding. Especially when it comes to algorithms. If you (carefully) inspect the given problem, you will realize that you can factor out the last iteration of the outer `for` loop and hence replace the "useless" return with a useful one (see [this answer](https://stackoverflow.com/a/42206068/3767239)). – a_guest Mar 07 '18 at 14:07
36

As @BoristheSpider pointed out you can make sure the second return statement is semantically unreachable:

private static int oneRun(int range) {
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    int count = 0;

    while (true) {
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                return count;
            }
        }
        count++;
    }
}

Compiles & runs fine. And if you ever get an ArrayIndexOutOfBoundsException you'll know the implementation was semantically wrong, without having to explicitly throw anything.

l0b0
  • 55,365
  • 30
  • 138
  • 223
  • 1
    Exactly: The condition is not actually used to control the loop, so it shouldn't be there. However, I would write this as `for(int count = 0; true; count++)` instead. – cmaster - reinstate monica Feb 13 '17 at 11:30
  • 3
    @cmaster: you can omit the `true`: `for(int count = 0; ; count++) …` – Holger Feb 13 '17 at 13:28
  • @Holger Ah. I wasn't sure on that because this is about java, and I haven't touch that language for years since I'm much more into C/C++. So, when I saw the use of `while(true)`, I thought that maybe java is a bit more strict in this regard. Nice to know there was no need to worry... Actually, I like the omitted `true` version much better: It makes it visually clear that there is absolutely no condition to look at :-) – cmaster - reinstate monica Feb 13 '17 at 14:02
  • 3
    I'd prefer not to switch to "for". A "while-true" is an obvious flag that the block is expected to loop unconditionally, unless and until something inside breaks it. A "for" with an omitted condition doesn't communicate as clearly; it could be overlooked more easily. – Corrodias Feb 13 '17 at 21:38
  • @Corrodias Agreed, I'd only switch to a `for` loop if there was some measurable performance impact, which with a modern compiler I would not expect to be the case. – l0b0 Feb 13 '17 at 21:41
  • The first step should be to use a `Set` though. Then the `while(true)` will be removed easily. See http://stackoverflow.com/a/42224287/669586. – Sulthan Feb 14 '17 at 11:01
  • @Sulthan That would belong on codereview.SE, and not what OP asked about. – l0b0 Feb 14 '17 at 11:04
  • 1
    @l0b0 The whole question would probably be better for code review considering the warning is related to code quality. – Sulthan Feb 14 '17 at 11:10
18

Since you asked about breaking out of two for loops, you can use a label to do that (see the example below):

private static int oneRun(int range) {
    int returnValue=-1;

    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    OUTER: for (int count = 1; count <= range; count++) { // Run until return.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count; i++) { // Check for past occurence and return if found.
            if (rInt[i] == rInt[count]) {
                returnValue = count;
                break OUTER;
            }
        }
    }
    return returnValue;
}
David Choweller
  • 1,060
  • 1
  • 8
  • 7
  • Wouldn't this fail to compile because `returnValue` can be used uninitialized? – pts Feb 12 '17 at 20:54
  • 1
    Most likely. The object of the post was to show how to break out of both loops, as the original poster had asked about this. The OP was sure that execution would never get to the end of the method, so what you brought up can be fixed by initializing returnValue to any value when it is declared. – David Choweller Feb 12 '17 at 21:03
  • 4
    aren't Labels that kind of things you better avoid by any means? – Serverfrog Feb 13 '17 at 13:29
  • 2
    I believe you're thinking of gotos. – David Choweller Feb 13 '17 at 14:15
  • 4
    Labels in a high(-ish) level programming language. *Absolutely barbaric...* – xDaizu Feb 13 '17 at 15:14
  • http://stackoverflow.com/questions/11133127/why-it-is-a-bad-practice-to-use-break-continue-labels-in-oop-e-g-java-c – David Choweller Feb 13 '17 at 15:22
  • If you don't want to use a Label you could just do `returnValue = count; count = range+1; break;` – Luke Feb 14 '17 at 11:02
13

While an assert is a good fast solution. In general this kind of problems means that your code is too complicated. When I am looking at your code, it's obvious that you don't really want an array to hold previous numbers. You want a Set:

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);
previous.add(randomInt);

for (int count = 1; count <= range; count++) {
    randomInt = generator.nextInt(range);
    if (previous.contains(randomInt)) {
       break;
    }

    previous.add(randomInt);
}

return previous.size();

Now note that what we are returning is actually the size of the set. The code complexity has decreased from quadratic to linear and it is immediately more readable.

Now we can realize that we don't even need that count index:

Set<Integer> previous = new HashSet<Integer>();

int randomInt = generator.nextInt(range);

while (!previous.contains(randomInt)) {          
    previous.add(randomInt);      
    randomInt = generator.nextInt(range);
}

return previous.size();
Sulthan
  • 128,090
  • 22
  • 218
  • 270
8

As your return value is based on the outer loop's variable you could simply alter the outer loop's condition to count < range and then return this last value (which you've just omitted) at the end of the function:

private static int oneRun(int range) {
    ...

    for (int count = 1; count < range; count++) {
        ...
    }
    return range;
}

This way you don't need to introduce code that will never be reached.

a_guest
  • 34,165
  • 12
  • 64
  • 118
  • But wouldn't this require a break out of the two layers of loops when the match is found? I don't see a way to reduce to one for statement, a new random number needs to be generated and compared to the ones before it before another is generated. – Oliver Benning Feb 14 '17 at 01:21
  • You're still left with two nested for loops (I just omitted the second one `...`) but the outer loop has been reduced by its last iteration. The case corresponding to this last iteration is handled separately by the very last return statement. Although this is an elegant solution for your example there are scenarios in which this approach becomes more difficult to read; if for example the return value depended on the inner loop then - instead of single return statement at the end - you would be left with an additional loop (representing the inner loop for the outer loop's last iteration). – a_guest Feb 14 '17 at 07:35
5

Use a temp variable, for instance "result" , and remove the inner return. Change the for loop for a while loop with the proper condition. To me it's always more elegant to have only one return as the last statement of the function.

David
  • 69
  • 3
  • Well, looks like the very inner if could be the outer while condition. Keep in mind that there's always a while equivalent to a for (and more elegant since you're not planning on always go through all the iterations). Those two nested for loops can be simplified for sure. All that code smells "too complex". – David Feb 12 '17 at 05:56
  • @Solomonoff'sSecret Your statement is absurd. It implies we should "in general" aim for two or more return statements. Interesting. – David Feb 13 '17 at 07:10
  • @Solomonoff'sSecret It's a matter of preferred programming styles, and regardless of what's cool this week which was lame last week, there are definite pros to having only one exit point, and at the end of the method (just think of a loop with many `return result` sprinkled around within it and then think of now wanting to do one more check on the found `result` before returning it, and this is just an example). There can also be cons but such a broad statement as "In general, there's no good reason to have only one return" doesn't hold water. – SantiBailors Feb 13 '17 at 13:19
  • @David Let me rephrase: there's no general reason to have only one return. In particular cases the might be reasons but typically it's cargo cult at its worst. – Reinstate Monica Feb 13 '17 at 15:15
  • @SantiBailors Let's compare apples to apples. Returns sprinkled throughout is better than assigning to a return variable throughout and returning it at the end because the former is simpler and more directly expresses what the code does. Returns throughout, however, suggests the complexity of the code is too high. That problem is solved by refactoring the code in a more fundamental way than to hide the fact that the code logically returns from many places. – Reinstate Monica Feb 13 '17 at 15:16
  • 1
    It is a matter of what makes the code more readable. If in your head you say "if it is this, return what we found; otherwise keep going; if we didn't find something return this other value". Then your code should have two return values. Always code what makes it more immediately understandable to the next developer. The compiler has only one return point from a Java method even if to our eyes it looks like two. – Rick Ryker Feb 13 '17 at 18:11
  • @SantiBailors There are definite pros for one return but the same problems solved by one return are also solved by splitting code into multiple functions. – Sulthan Feb 14 '17 at 15:49
3

Maybe this is an indication that you should rewrite your code. For example:

  1. Create an array of integers 0 .. range-1. Set all the values to 0.
  2. Perform a loop. In the loop, generate a random number. Look in your list, at that index, to see if the value is 1 If it is, break out of the loop. Otherwise, set the value at that index to 1
  3. Count the number of 1s in the list, and return that value.
3

Methods that have a return statement and have a loop/loops inside them always require a return statement outside the loop(s). Even if this statement outside the loop is never reached. In such cases, in order to avoid unnecessary return statements, you could define a variable of the respective type, an integer in your case, at the beginning of the method i.e. before and outside the respective loop(s). When the desired result inside the loop is reached, you can ascribe the respective value to this pre-defined variable and use it for the return statement outside the loop.

Since you want your method to return the first result when rInt[i] equals rInt[count], implementing only the above-mentioned variable is not enough because the method will return the last result when rInt[i] equals rInt[count]. One options is to implement two "break statements" that are called when the we have the desired result. So, the method will look something like this:

private static int oneRun(int range) {

        int finalResult = 0; // the above-mentioned variable
        int[] rInt = new int[range + 1];
        rInt[0] = generator.nextInt(range);

        for (int count = 1; count <= range; count++) {
            rInt[count] = generator.nextInt(range);
            for (int i = 0; i < count; i++) {
                if (rInt[i] == rInt[count]) {
                    finalResult = count;
                    break; // this breaks the inside loop
                }
            }
            if (finalResult == count) {
                break; // this breaks the outside loop
            }
        }
        return finalResult;
    }
Lachezar
  • 1
  • 3
2

I agree that one should throw an exception where unreachable statement occurs. Just wanted to show how the same method can do this in more readable way (java 8 streams required).

private static int oneRun(int range) {
    int[] rInt = new int[range + 1];
    return IntStream
        .rangeClosed(0, range)
        .peek(i -> rInt[i] = generator.nextInt(range))
        .filter(i -> IntStream.range(0, i).anyMatch(j -> rInt[i] == rInt[j]))
        .findFirst()
        .orElseThrow(() -> new RuntimeException("Shouldn't be reached!"));
}
Sergey Fedorov
  • 2,169
  • 1
  • 15
  • 26
-1
private static int oneRun(int range) {
    int result = -1; // use this to store your result
    int[] rInt = new int[range+1]; // Stores the past sequence of ints.
    rInt[0] = generator.nextInt(range); // Inital random number.

    for (int count = 1; count <= range && result == -1; count++) { // Run until result found.
        rInt[count] = generator.nextInt(range); // Add randint to current iteration.   
        for (int i = 0; i < count && result == -1; i++) { // Check for past occurence and leave after result found.
            if (rInt[i] == rInt[count]) {
                result = count;
            }
        }
    }
    return result; // return your result
}
haider_kazal
  • 3,448
  • 2
  • 20
  • 42
blabla
  • 1
  • The code is also inefficient since one will do **a lot** of `result == -1` checks which can be omitted with the `return` inside the loop... – Willem Van Onsem Feb 16 '17 at 18:29