Note:

In this Notebook, I used besides Code cells also Markdown cells for formatted text - like this one. This is a great way to write down your thoughts, concepts or documentation as you work with data - or like here, mix instructions with code.

You can edit these Markdown cells yourself: Simply double-click to open the Edit mode. When you're done, you can "execute" a Markdown cell to show it as nicely formatted text.

For more, see the link to Markdown Help in the Help menu.

This exercise makes use of the "random" package, included in Python. Take a look at the documentation for more information:

https://docs.python.org/3/library/random.html

The following cell with an import statement has to be executed once.

In [1]:
# The following "import" line makes functions of the "random" package available.
import random
# Standard deviation, for exercise 4:
from statistics import stdev

Another note:

Jupyter notebooks can show you the built-in help/documentation of Python functions (and classes, modules, ...) in a nice fashion at the bottom of the screen. All you have to do is write the name of the function and append a question mark ?. Try it below!

You can use two question marks ?? to get even more help, sometimes the complete source code of the function.

In [2]:
random.randint?

Exercise 1) A simple while-loop exercise

Create two random integers, a and b, in the range of [1, 100]. Print the initial values of a and b. Write a while loop that:

  1. Runs as long as the difference of a and b are more than 5.
    HINT: Use the builtin abs() function. It's always available. See:
    https://docs.python.org/3/library/functions.html#abs
  2. Chooses a new random number for the SMALLER one of a or b.
  3. Print the actual/new values of a and b at the end of each loop iteration.

Let your program run a couple of times to see how it works.

In [3]:
# An example of using a function of the "random" package,
# creates a random integer in the range [10,20]:
r = random.randint(10,20)
print("The randomly chosen value between 10 and 20 is: {}".format(r))
# You may as well delete this example or comment it out.
The randomly chosen value between 10 and 20 is: 10
In [4]:
# Create initial random integers:
a = random.randint(1, 100)
b = random.randint(1, 100)
print(f'Initial: a = {a:3}, b = {b:3}')
# use abs() to get the absolute distance without sign:
while abs(a - b) > 5:
    # Compare the two values and generate one anew:
    if a < b:
        a = random.randint(1, 100)
    else:
        b = random.randint(1, 100)
    # Print the actual state at the end of this loop:
    print(f'Updated: a = {a:3}, b = {b:3}')
# Additionally: Print final state (equal to last loop iteration)
print(f'Final:   a = {a:3}, b = {b:3}')
Initial: a =  42, b =  51
Updated: a =  34, b =  51
Updated: a =  87, b =  51
Updated: a =  87, b =  58
Updated: a =  87, b =  85
Final:   a =  87, b =  85

Exercise 2) Extend your own code

Change your code to count, how many iterations were used to complete the loop.

You can modify your code above or make a copy of it in a new cell and work in there. Look at the Edit menu or the 3rd to 5th menu button in the toolbar to copy&paste entire cells.

In [5]:
a = random.randint(1, 100)
b = random.randint(1, 100)
# Initialize counter with value 0:
counter = 0
print(f'Initial: a = {a:3}, b = {b:3}')
while abs(a - b) > 5:
    if a < b:
        a = random.randint(1, 100)
    else:
        b = random.randint(1, 100)
    # Increment counter, equal to `counter = counter + 1`
    counter += 1
    print(f'Updated: a = {a:3}, b = {b:3} (iteration {counter})')
print(f'Final:   a = {a:3}, b = {b:3} after {counter} iterations.')
Initial: a =  27, b =  93
Updated: a =   2, b =  93 (iteration 1)
Updated: a =  25, b =  93 (iteration 2)
Updated: a =  95, b =  93 (iteration 3)
Final:   a =  95, b =  93 after 3 iterations.

Exercise 3) Extend even further

  • Embrace your code by a for loop, running 100 times.
  • Save the loop counts (from Exercise 2) of all 100 runs in a list.
  • To see, how a for loop can be made to run 100 times, take a look at "The range() Function" in the Python tutorial:
    https://docs.python.org/3/tutorial/controlflow.html#the-range-function

    In short, it should look something like this:

    for i in range(100):
        #... your pre-existing code...
    
  • At the end, print the minimum, maximum and average loop counts.
    HINT: Use the builtin min(), max() and sum() functions.

  • You can also calculate the standard deviation with the stdev() function from the statistics module (we imported that above).
In [6]:
# Create an empty list to be filled:
loop_counts = []
# Run the inner code 100 times:
for i in range(100):
    a = random.randint(1, 100)
    b = random.randint(1, 100)
    counter = 0
    while abs(a - b) > 5:
        if a < b:
            a = random.randint(1, 100)
        else:
            b = random.randint(1, 100)
        counter += 1
    # append the current counter value to the loop_counts list:
    loop_counts.append(counter)
    # Commented out, so we see the aggregated results only.
    # Uncomment to see the final state and iteration count of each loop:
    print(f'Final:   a = {a:3}, b = {b:3} after {counter} iterations.')

# An empty line to separate outputs:
print()
# Print "statistics" values
# (I'm lazy, so I didn't save them in variables first)
print("           Minimum:", min(loop_counts))
print("           Maximum:", max(loop_counts))
# The mean is simply the sum divided by the number of values,
# here the length the list
# Note: You COULD just divide by 100, but that will bite you
# later whenever you change the number of iterations.
print("              Mean:", sum(loop_counts) / len(loop_counts))
# Also interesting:
print("Standard Deviation:", stdev(loop_counts))
Final:   a =  98, b =  99 after 25 iterations.
Final:   a =  95, b =  96 after 3 iterations.
Final:   a =  91, b =  95 after 17 iterations.
Final:   a =  29, b =  25 after 4 iterations.
Final:   a =  98, b =  96 after 6 iterations.
Final:   a =  88, b =  86 after 6 iterations.
Final:   a =  89, b =  86 after 10 iterations.
Final:   a =  96, b =  99 after 17 iterations.
Final:   a =  88, b =  89 after 1 iterations.
Final:   a =  97, b =  96 after 17 iterations.
Final:   a =  98, b =  93 after 7 iterations.
Final:   a = 100, b =  96 after 30 iterations.
Final:   a =  79, b =  79 after 2 iterations.
Final:   a =  65, b =  64 after 0 iterations.
Final:   a =  81, b =  86 after 12 iterations.
Final:   a =  79, b =  74 after 6 iterations.
Final:   a =  94, b =  96 after 1 iterations.
Final:   a =  72, b =  71 after 0 iterations.
Final:   a =  95, b =  95 after 14 iterations.
Final:   a =  92, b =  94 after 5 iterations.
Final:   a =  98, b =  96 after 4 iterations.
Final:   a =  91, b =  95 after 15 iterations.
Final:   a =  71, b =  73 after 7 iterations.
Final:   a =  90, b =  94 after 0 iterations.
Final:   a =  90, b =  85 after 2 iterations.
Final:   a =  82, b =  87 after 3 iterations.
Final:   a =  57, b =  61 after 0 iterations.
Final:   a =  64, b =  66 after 2 iterations.
Final:   a = 100, b =  96 after 15 iterations.
Final:   a =  97, b =  92 after 10 iterations.
Final:   a =  98, b = 100 after 10 iterations.
Final:   a =  92, b =  96 after 26 iterations.
Final:   a =  98, b = 100 after 9 iterations.
Final:   a =  96, b =  96 after 2 iterations.
Final:   a =  89, b =  86 after 31 iterations.
Final:   a =  75, b =  73 after 3 iterations.
Final:   a =  82, b =  82 after 3 iterations.
Final:   a =  79, b =  78 after 6 iterations.
Final:   a =  69, b =  68 after 4 iterations.
Final:   a =  84, b =  89 after 8 iterations.
Final:   a =  92, b =  96 after 18 iterations.
Final:   a =  46, b =  49 after 1 iterations.
Final:   a =  87, b =  84 after 5 iterations.
Final:   a =  94, b =  91 after 6 iterations.
Final:   a =  82, b =  81 after 1 iterations.
Final:   a =  99, b = 100 after 9 iterations.
Final:   a =  95, b =  92 after 19 iterations.
Final:   a =  98, b =  93 after 32 iterations.
Final:   a =  96, b =  94 after 3 iterations.
Final:   a =  76, b =  73 after 1 iterations.
Final:   a =  18, b =  19 after 0 iterations.
Final:   a =  79, b =  84 after 3 iterations.
Final:   a =  99, b =  98 after 9 iterations.
Final:   a =  91, b =  86 after 11 iterations.
Final:   a =  96, b =  97 after 32 iterations.
Final:   a =  96, b =  95 after 9 iterations.
Final:   a =  99, b =  95 after 14 iterations.
Final:   a =  75, b =  75 after 1 iterations.
Final:   a =  99, b =  98 after 10 iterations.
Final:   a =  64, b =  66 after 1 iterations.
Final:   a =  95, b =  95 after 1 iterations.
Final:   a =  91, b =  95 after 8 iterations.
Final:   a = 100, b =  97 after 16 iterations.
Final:   a =  98, b =  94 after 1 iterations.
Final:   a =  96, b =  97 after 9 iterations.
Final:   a =  54, b =  59 after 0 iterations.
Final:   a =  49, b =  48 after 7 iterations.
Final:   a =  93, b =  92 after 8 iterations.
Final:   a =  95, b =  91 after 9 iterations.
Final:   a =  90, b =  93 after 3 iterations.
Final:   a =  86, b =  90 after 7 iterations.
Final:   a =  94, b =  93 after 6 iterations.
Final:   a =  78, b =  77 after 3 iterations.
Final:   a =  91, b =  86 after 4 iterations.
Final:   a =  27, b =  26 after 0 iterations.
Final:   a =  78, b =  83 after 7 iterations.
Final:   a =  99, b =  98 after 27 iterations.
Final:   a = 100, b =  97 after 45 iterations.
Final:   a =  93, b =  94 after 47 iterations.
Final:   a =  99, b = 100 after 27 iterations.
Final:   a =  65, b =  67 after 1 iterations.
Final:   a =  91, b =  88 after 1 iterations.
Final:   a =  99, b =  99 after 8 iterations.
Final:   a =  65, b =  60 after 0 iterations.
Final:   a =  55, b =  51 after 0 iterations.
Final:   a =  94, b =  98 after 11 iterations.
Final:   a =  75, b =  73 after 10 iterations.
Final:   a =  91, b =  87 after 10 iterations.
Final:   a =  80, b =  75 after 4 iterations.
Final:   a = 100, b =  96 after 76 iterations.
Final:   a =  94, b =  91 after 2 iterations.
Final:   a =  97, b =  92 after 11 iterations.
Final:   a =  95, b =  93 after 13 iterations.
Final:   a =  72, b =  71 after 2 iterations.
Final:   a =  93, b =  95 after 3 iterations.
Final:   a =  74, b =  79 after 4 iterations.
Final:   a =  74, b =  78 after 1 iterations.
Final:   a =  96, b =  94 after 18 iterations.
Final:   a =  86, b =  89 after 7 iterations.
Final:   a =  92, b =  92 after 19 iterations.

           Minimum: 0
           Maximum: 76
              Mean: 9.64
Standard Deviation: 11.682257980165971

Exercise 4) Make it a function (optional, yet highly recommended)

It might seem silly, but what you probably did above - copying and pasting your own code - is a perfectly normal, yet error prone practice. Often enough, you will seemingly have a quicker result by just doing that.

However, this is also a good example, where a function would have been a good solution.

  • Take your code from the above Exercise 2 (copying it one last time) and put it inside a new function approaching_numbers().
    This might look like:

    def approaching_numbers():
        """Two random numbers approaching each other"""
        a = random.randint(1, 100)
        b = ...
        loop_count = 0
        while ...
    
  • Make the function return the loop count.
  • Test your function by calling it, saving the return value in a variable and printing its value.
    Roughly like this:

    c = approaching_numbers()
    print("The function returned:", c)
    
  • Once your function is working, you might want to remove or "comment out" some of the earlier print() lines - we don't really need them anymore, except for debugging if necessary.

  • Expand your approaching_numbers() function by making the lower and upper limit of the random.randint() function parameters that you can pass on.
    It should look like this:

    def approaching_numbers(lower, upper):
        """Two random numbers approaching each other"""
        a = random.randint(lower, upper)
        b = ...
    

    And then you can call your function like this, e.g. with an interval from 500 to 1000:

    c = approaching_numbers(500, 1000)
    
  • Define default values for the lower and upper parameters, such that these two lines do the same:

    c = approaching_numbers()
    c = approaching_numbers(1, 100)
    
In [7]:
# Version 1, without parameters
def approaching_numbers_no_params():
    a = random.randint(1, 100)
    b = random.randint(1, 100)
    counter = 0
    while abs(a - b) > 5:
        if a < b:
            a = random.randint(1, 100)
        else:
            b = random.randint(1, 100)
        counter += 1
    return counter
# call like this, you'll see the return value as output of this cell:
approaching_numbers_no_params()
Out[7]:
8
In [8]:
# Version 2, without mandatory parameters
def approaching_numbers_mandatory_params(lower, upper):
    a = random.randint(lower, upper)
    b = random.randint(lower, upper)
    counter = 0
    while abs(a - b) > 5:
        if a < b:
            a = random.randint(lower, upper)
        else:
            b = random.randint(lower, upper)
        counter += 1
    return counter
# call like this (examples):
out100 = approaching_numbers_mandatory_params(1, 100)
out1k = approaching_numbers_mandatory_params(1, 1000)
out10k = approaching_numbers_mandatory_params(1, 10000)
print("Range 1 to   100 needed", out100, "iterations")
print("Range 1 to  1000 needed", out1k, "iterations")
print("Range 1 to 10000 needed", out10k, "iterations")
Range 1 to   100 needed 9 iterations
Range 1 to  1000 needed 54 iterations
Range 1 to 10000 needed 4546 iterations
In [9]:
def approaching_numbers(lower = 1, upper = 100):
    """Two random integers approach each other
    
    Two initially randomly drawn integers are updated until
    their absolute difference is less than or equal to five.
    Only the smaller number is updated in every iteration.
    
    Parameters
    ----------
    lower, upper : int, optional
        Lower and upper bounds (inclusive) for random numbers.
        Default range is 1 to 100.
    
    Returns
    -------
    int
        Number of loop iterations until the two integers were
        close enough to each other.

    Notes
    -----
    Every good function has a so call docstring. That is the
    first string inside the (indented) function body, not saved
    into any variable. For simple functions, the description
    might be longer than the code itself.
    
    This docstring follows the style guide that is used for
    numpy [1] which I think is one of the clearest ways to do
    it. The most important thing about a docstring, though, is
    that it explains what a function does and how to use it.
    
    References
    ----------
    .. [1] https://numpydoc.readthedocs.io/en/latest/format.html
    """
    a = random.randint(lower, upper)
    b = random.randint(lower, upper)
    counter = 0
    while abs(a - b) > 5:
        if a < b:
            a = random.randint(lower, upper)
        else:
            b = random.randint(lower, upper)
        counter += 1
    return counter
In [10]:
# These two lines should work when you're done (uncomment to use):

print(approaching_numbers())
print(approaching_numbers(500, 1000))
5
11
In [11]:
# And since we have written a beautiful docstring, we can get help:
approaching_numbers?

Use your function for more:

  • Finally, take your code from Exercise 3 (not all of it, apparently) and rewrite it to use your approaching_numbers() function. Collect the returned values in a list and print minimum, maximum, mean and standard deviation.
  • Check your new code with different values used for the lower and upper bounds. Important: You should not need to change the approaching_numbers() function for this.
In [12]:
loop_counts = []
for i in range(100):
    # still any doubts why it's so nice to have a function?
    loop_counts.append(approaching_numbers())

print("           Minimum:", min(loop_counts))
print("           Maximum:", max(loop_counts))
print("              Mean:", sum(loop_counts) / len(loop_counts))
print("Standard Deviation:", stdev(loop_counts))
           Minimum: 0
           Maximum: 64
              Mean: 8.78
Standard Deviation: 11.119715860042504

(super optional) Write a function, using your function:

  • Put your new "evaluation" code in its own new function evaluate_approach().
  • Make the lower and upper bounds parameters of that function as well and pass them through when you call approaching_numbers().
  • Add another parameter n for the number of iterations of your for-loop.
  • Try it out with different parameters for the range of random numbers and the number of iterations. Does the output match your expectations?
In [13]:
def evaluate_approach(n = 100, lower = 1, upper = 100):
    """Evaluate the output of approaching_numbers()
    
    Parameters
    ----------
    n : int
        Number of executions before evaluating the results.
    lower, upper : int, optional
        Lower and upper bounds (inclusive) for random numbers
        in approaching_numbers(). Default range is 1 to 100.
        
    See Also
    --------
    approaching_numbers :
        The inner function doing the actual work.
    """
    loop_counts = []
    for i in range(n):
        # Important: Don't forget to pass through `lower` and `upper`
        loop_counts.append(approaching_numbers(lower, upper))

    print(f"After {n} iterations, loop counts are as follows:")
    print("           Minimum:", min(loop_counts))
    print("           Maximum:", max(loop_counts))
    print("              Mean:", sum(loop_counts) / len(loop_counts))
    print("Standard Deviation:", stdev(loop_counts))
In [14]:
# Just saying... you can also get help like this:
help(evaluate_approach)
Help on function evaluate_approach in module __main__:

evaluate_approach(n=100, lower=1, upper=100)
    Evaluate the output of approaching_numbers()
    
    Parameters
    ----------
    n : int
        Number of executions before evaluating the results.
    lower, upper : int, optional
        Lower and upper bounds (inclusive) for random numbers
        in approaching_numbers(). Default range is 1 to 100.
        
    See Also
    --------
    approaching_numbers :
        The inner function doing the actual work.

In [15]:
evaluate_approach()
After 100 iterations, loop counts are as follows:
           Minimum: 0
           Maximum: 45
              Mean: 9.08
Standard Deviation: 8.816513431675194
In [16]:
evaluate_approach(100)
After 100 iterations, loop counts are as follows:
           Minimum: 0
           Maximum: 99
              Mean: 10.78
Standard Deviation: 13.977312063671345
In [17]:
evaluate_approach(10000)
After 10000 iterations, loop counts are as follows:
           Minimum: 0
           Maximum: 118
              Mean: 9.4244
Standard Deviation: 10.83616292248729
In [18]:
# THIS MAY RUN A FEW SECONDS:
evaluate_approach(n = 1000, lower = -20000, upper = +20000)
After 1000 iterations, loop counts are as follows:
           Minimum: 1
           Maximum: 34057
              Mean: 4147.785
Standard Deviation: 4503.4751620621055