4

Is there an elegant way in Python to detect whether a break condition was engaged at the last iteration or never at all?

C++ example:

int k, n = 10;
for (k = 0; k < n; k++)
    if (condition) break;

if (k == n) cout << "Never broke\n";
else        cout << "Broke at " << k << '\n';

Python example:

n = 10
for k in range(n):
    if condition: break

if k == n: print("Never broke")
else:      print("Broke at", k)

In Python, we don't know whether condition was true at the last iteration since k is 9 in both cases.

Why not just use range(n + 1) instead? Because on some contexts, we might get an "index out of range" error when k is n.

One possible workaround is to use a sentinel value as shown below, but is there a better way?

n, flag = 10, True
for k in range(n):
    if condition:
        flag = False
        break

if flag: print("Never broke")
else:    print("Broke at", k)
visitor
  • 672
  • 6
  • 17

2 Answers2

5

Use for/else. That's specifically what it's for.

for k in range(n):
    if condition:
       print("Broke at", k)
       break
else:
    print("Never broke")
kindall
  • 178,883
  • 35
  • 278
  • 309
  • 3
    I always describe `for`/`else` as being for the needle in a haystack case: If you find the needle, you `break`, and if there was no needle, the `else` case is what tells you the haystack is nothing but hay. – ShadowRanger Dec 13 '17 at 01:38
1

Not necessarily better, but often you can condense the loop into a use of any or all (for when you only care if a value was found) or next (for when you care about the value found).

For example, to find the first item satisfying some test, or None if no such item exists, you can use two-arg next plus a generator expression:

needle = next((x for x in haystack if isneedle(x)), None)
if needle is not None:
    ... do stuff with needle ...
else:
    ... no needle ...

or roughly equivalently with one-arg next and exception handling:

try:
    needle = next(x for x in haystack if isneedle(x))
except StopIteration:
    ... no needle ...
else:
    ... do stuff with needle ...

A realistic use case might be identifying a prime number via trial division. In that case, you don't care what factor you found, you just care that there was a factor, so you could write a testing function as:

num = ...
isnumprime = num >= 2 and all(num % f != 0 for f in range(2, int(num ** 0.5) + 1))

for/else is a perfectly adequate way to do this (see other answers), but any/all/next (usually with generator expressions) can be cleaner in specific contexts.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271