<jupyter_start><jupyter_text>More Ways to Loop `while` LoopsIn addition to [for()](https://docs.python.org/3/tutorial/controlflow.htmltut-for) loops, which iterate over an arithmetic progression, Python offers a way to iterate indefinitely as long as a particular Boolean expression remains true. It's called a [while()](https://docs.python.org/3/reference/compound_stmts.htmlwhile) loop. Here's an example:<jupyter_code>n = 10
while n > 0:
    print(n)
    n = n - 1
print("The program is complete")<jupyter_output><empty_output><jupyter_text>If your reaction is: "*Why is that needed? I could accomplish the same thing with a `for()` loop.*" then excellent! You're becoming a coder! Try this next example to appreciate when a [while()](https://docs.python.org/3/reference/compound_stmts.htmlwhile) loop becomes useful:<jupyter_code>n = 1
while n > 0:
    n = int(input("Enter an integer greater than 0:"))
    print("You entered:", n)
print("The program is complete.")<jupyter_output><empty_output><jupyter_text>*Note: It's easy to crash the program above by entering something other than an integer. Telling you it's easy to crash a program should immediately make you want to try to do it :-)*  Nested LoopsIt's possible to have loops inside of loops, as long as you carefully manage the indentation of the code.Let's write a program that prints out the times table (starting with `1 x 1` and going up to `10 x 10`. We could start like this:<jupyter_code>for i in range(1, 11):
    for j in range(1, 11):
        print(i * j)<jupyter_output><empty_output><jupyter_text>Key points:* We often refer to code written like this as having an *inner* loop and an *outer* loop.* The *inner* loop is this one: `for j in range(1,11):`* The *outer* loop is this one: `for i in range(1,11):`* Notice that the [range()](https://docs.python.org/3/tutorial/controlflow.htmltut-range) function goes from `1` (the starting point) to `10` (one less than the end point)* The indentation is very important!It's a decent start, but it doesn't look very much like a times "table". To do that, we need to print out an entire row within the inner loop, then print a new line within the outer loop. Explore this code:<jupyter_code>for i in range(1, 11):
    for j in range(1, 11):
        print(i * j, end=' ')
    print()<jupyter_output><empty_output><jupyter_text>I'm making use of an important feature in Python called *optional arguments*. We'll explore optional arguments later in the course, but for now it's enough to say that when you call a function (like [print()](https://docs.python.org/3/library/functions.html)), there are things you can pass to the function that are optional. In the case of [print()](https://docs.python.org/3/library/functions.html), the argument `end = ` is optional. If you don't use it (like all the examples of [print()](https://docs.python.org/3/library/functions.html) we've seen up to this point), then Python puts a newline after what gets printed. If you specify something using `end = `, then what you specify will be printed (a space in this case) instead of a newline.To really make it pop, let's use Python's [f-strings](https://docs.python.org/3/tutorial/inputoutput.html) (which we discussed in notebook [02_operations04.ipynb](./02_operations04.ipynb)).<jupyter_code>for i in range(1, 11):
    for j in range(1, 11):
        print(f"{i*j:3d}", end=' ')
    print()<jupyter_output><empty_output><jupyter_text>`break`The [break and continue](https://docs.python.org/3/tutorial/controlflow.htmltut-break) statements are two ways to change the behavior of a loop while its executing. Here's what the Python documentation has to say:* *The break statement, like in C, breaks out of the innermost enclosing for or while loop.** *The continue statement, also borrowed from C, continues with the next iteration of the loop*Here's a simple example of using [break](https://docs.python.org/3/tutorial/controlflow.htmltut-break). It will try to print every number from `1` to `10`, but will stop early if it encounters the number `5`. ***NOTE: Before you run the code, think about this question: Do you think it will actually print the number `5` or not? Run the code to check your answer.***<jupyter_code>for i in range(1, 11):
    if i == 5:
        break
    print(i)<jupyter_output><empty_output><jupyter_text>Here's a more complex example that determines if an integer is [prime](https://www.mathsisfun.com/definitions/prime-number.html). It uses a [while()](https://docs.python.org/3/reference/compound_stmts.htmlwhile) loop, an [if](https://docs.python.org/3/tutorial/controlflow.htmlif-statements) statement, and a [break](https://docs.python.org/3/tutorial/controlflow.htmltut-break) statement. There are comments in the code to aid with clarity, which gives you an idea of how to express your thoughts in code comments. Note: I'm using the [modulo operator](https://docs.python.org/3.3/reference/expressions.htmlbinary) (`%`) to see if one number divides evenly into another number.<jupyter_code># We'll skip the number 2, since it's the only even prime number.

# Check every integer from 2, up to the candidate number. If none of them
# divides evenly into the candidate, then its only factors are one and itself;
# therefore it's prime. If we find a number that divides evenly into the
# candidate, then we can quit the loop right away, because we know the
# candidate is not prime.

n = int(input("Enter an integer greater than 2: "))
divisor = 2

while divisor < n:
    if n % divisor == 0:
        break
    divisor += 1

# if divisor == n, it means we didn't break out of the loop
if divisor == n:
    print(n, "is prime.")

# if divisor is not equal to n, it means we found a divisor that goes evenly
# into n, so n is not prime
else:
    print(n, "is not prime")<jupyter_output><empty_output><jupyter_text>*Line 14 is an example of an [augmented assignment statement](https://docs.python.org/3/reference/simple_stmts.htmlaugassign); a shorthand way of writing: `divisor = divisor + 1`.*Prime numbers are a critical part of modern cryptography. It's quite easy to multiply two large prime numbers together (say, `p1` and `p2`) to get a composite number we'll call `C`. However, for really large numbers (think thousands of digits) it's super-hard to start with `C` and figure out the original `p1` and `p2`.Our little prime calculator works pretty well, and it's relatively easy to follow, but it's very inefficient. It can tell you a number is **not** prime pretty quickly, but for large numbers it can take a seriously long time to tell you the number is prime. For example, this modest 10-digit prime number takes a couple of minutes to produce a result: `1000000349`.We can make our calculator much more efficient in a few ways:* By only examining divisors up to and including the square root of the candidate, rather than going all the way to the candidate itself. If there's a divisor greater than `sqrt(n)` that goes evenly into `n`, then the result of `n` divided by that divisor would have been discovered somewhere less than or equal to `sqrt(n)`. To use Python's [sqrt()](https://docs.python.org/3/library/math.htmlmath.sqrt) function, we'll need to import the [math](https://docs.python.org/3/library/math.html?highlight=sqrtmodule-math) module.* We can start by checking if the candidate is an even number (`n % 2 == 0`). If so, we know right away that it can't be prime.* Once we rule out `n` being an even number, we don't need to bother checking any even divisors. If a number doesn't divide evenly by 2, then it won't divide evenly by a multiple of 2 (4, 6, 8, etc.).Using these techniques our new program is shown below. It makes use of some very interesting nested control structures. Try it with our original 10-digit prime (`1000000349`). Then try this 15-digit prime: `939147656661311`. Not bad for a few lines of Python :-)<jupyter_code>import math

n = int(input("Enter an integer greater than 2: "))

if n % 2 == 0:
    print(n, "is not prime")

else:
    divisor = 3
    while divisor <= math.sqrt(n):
        if n % divisor == 0:
            break
        divisor += 2

    if divisor > math.sqrt(n):
        print(n, "is prime.")
    else:
        print(n, "is not prime")<jupyter_output><empty_output><jupyter_text>`continue`The [continue](https://docs.python.org/3/tutorial/controlflow.htmltut-break) statement allows you to skip an iteration cycle of a loop and move on to the next one. Here's a simple example:<jupyter_code># Print even numbers less than or equal to 10

for i in range(1, 11):
    if i % 2 != 0:
        continue
    print(i)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10127314814814815, 'fraction_numerical': 0.01747685185185185, 'mean_word_length': 4.3110018438844495, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 40, 'https://': 19, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '224072', 'license': 'MIT', 'n_tokens_mistral': 2675, 'n_tokens_neox': 2474, 'n_words': 1158, 'path': 'content/05_iteration01.ipynb', 'repo_name': 'geozeke/notebooks', 'size': '8640'}