NUMERICAL COMPUTING IS FUN

A guide to principles of computer science and numerical computing for all ages

As much as this series is to educate aspiring computer programmers and data scientists of all ages and all backgrounds, it is also a reminder to myself. After playing with computers and numbers for nearly 4 decades, I've also made this to keep in mind how to have fun with computers and maths.


OVERVIEW OF PART 4

In this fourth part, we will continue with putting in to practice what we learned in Part 1, Part 2, and Part 3 of the series.

We will go deeper into the meaning of algoritms, specifically in the context of the idea of automation. Most computer programs are ways to automate things so that humans can do human stuff instead. Or at least that's the idea.

As a reminder from Part 1, prime numbers are the numbers that all other numbers are made of. This means that any number that is only divisible by 1 or itself, is a prime number. Consequently any number that is divisible by a number other than 1 or itself, is not a prime number.

Remember the way we defined algorithm as something like a factory? Automation has to do with all those algorithms combined, as well all of their individual components. All those tiny pieces of code together make up what we refer to as automation.

PART 4 : Process Automation

We've already created simple algorithms and were left in the third version of our algo in Part 3. Before continuing to build our algo further, let's learn about generating numbers. It is a perfect example of how computers help us automated tedious processess.

4.1. Generating Numbers

Before moving on to the main section of this part, which will cover loops, another flow control method, let's become familiar with the idea and have some number generation fun.

Number generation will become handy soon, so we don't have to key in the many numbers we want to check in terms of if they are prime or not. To do this, we will use a for statement. But let's first learn about range, a nifty little function that comes with python.

In [21]:
range(10)
Out[21]:
range(0, 10)

At the simplest, range takes a number and creates a sequence of numbers from 0 to the input number. In this case, even though we can't see the numbers yet, we've created a sequence 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

You might wonder why we don't get a 10 but stop at 9 even though we input 10. In Python and most other programming languages counting always starts from 0. Now, let's access the numbers we're generating.

In [22]:
for i in range(10):
    print(i)
0
1
2
3
4
5
6
7
8
9

Let's see what we did here. First with for we basically say that we want to do something for a number of items. Then with i we say that each time an item is picked, i will represent it. In other words, we can use i to access it inside the loop. With range(10) we create a sequence of numbers from 0 through 9. As you can see, the print(i) has leading spaces to it, which means that it's handled inside the loop. Note that i can be called anything you like.

In [23]:
for number in range(10):
    print(number)
0
1
2
3
4
5
6
7
8
9

range can be used to create any sequence of integers by defining the starting and ending positions of the sequence.

In [24]:
for i in range(11, 18):
    print(i)
11
12
13
14
15
16
17

We can also add a 'step' argument, which gives us even more control over the range of numbers we want to create. For example with step argument 2, we will get every other number in a range:

In [25]:
for i in range(2, 20, 2):
    print(i)
2
4
6
8
10
12
14
16
18

This way we only get the even numbers between 2 and 2. Let's try the same for odd numbers.

In [26]:
for i in range(1, 20, 2):
    print(i)
1
3
5
7
9
11
13
15
17
19

You've now learned a very useful and often applicable process automation; number generation. We've learn how to write any sequence of numbers, including just even or odd numbers.

There are many other ways you can use to create numbers, including random numbers, but this will be more than enough for what we want to do. Let's move on to the next section and learn about loops.

As you see, loops are just as easy and intuitive to use in Python language as everything else we've learn so far. A for loop gives us a useful way to say that some process will go on for as long as something is true. Consider this example:

4.2. Storing something in a variable

Now that you've learn yet another fundamental building block of numerical computing, we can go back to our prime number algorithm. Let's start putting what we've learn into use in our algo and also introduce one more new concept (I promise it's the last one for a while), storing something on to a variable. Storing something in to the computer memory is nothing like storing something in to human memory.

Even though we talk about storing something, it'a also nothing like storing something in to a safety deposit box where things will be kept for a long time. Computer memory is a temporary storage for something we're going to use as part of our computer program. Let's see some examples.

In [30]:
storage = 1
In [31]:
print(storage)
1

Basically anything in Python can be stored in a variable. It's very simple. Also, we don't have to use the print to access the contents of a variable.

In [32]:
storage
Out[32]:
1

In the next two examples you will get a taste for how anything can be stored into a variable to access it later.

In [40]:
answer =  1 is 2
answer
Out[40]:
False
In [41]:
print_hello = print('hello')
print_hello
hello

This ability to store things into variables is very powerful, particularly when we want to use the same value many times. For example, we could use this approach to simplify the most recent version of our algo. Take a note below how we are using the same operation twice:

In [42]:
def third_algo(left, right):
    
    # it will not rain
    if left % right is 0:  # < -- here first time
        return True
    
    # it will rain a little
    elif left % right is 1:  # < -- here second time
        return 
    
    # it will rain heavily
    else: 
        return False

Now let's make a slight simplifcation by storing the operation we do twice in to memory first.

In [43]:
def fourth_algo(left, right):
    
    stored_value = left % right
    
    # it will not rain
    if  stored_value is 0:
        return True
    
    # it will rain a little
    elif stored_value is 1:
        return 
    
    # it will rain heavily
    else: 
        return False

Before wrapping up for now, let's put all that we've learn together in one example.

3.5. Putting it All Together

In this following section the length of our algoritm (function) is growing. But if you look carefully, you see that the changes we make are very small in fact. Moreover, we are only making changes that you've already learn so far.

In [50]:
# first we create a range of numbers
numbers = range(1, 10)

# then we create a loop
for number in numbers: 
    
    # then we perform the modulus operation 
    result = number % number
    
    # then we create a conditional statement for cases when it's true
    if result is 0: 
        print(True)
        
    # and finish with else for cases when it's false 
    else: 
        print(False)
True
True
True
True
True
True
True
True
True

Obviously we are getting True as result everytime because we are always having both the right number and the left number the same (e.g. 1 % 1, 2 % 2...).

Let's make a slight modification to take us step closer to something that will help us a great deal in finding prime numbers later. This time I'm removing the comments to keep the code neat.

In [51]:
left = 20
right_numbers = range(1, 20)

for right in numbers: 
    result = left % right
    
    if result is 0:         
        print(True)
        
    else: 
        print(False)
True
True
False
True
True
False
False
False
False

So what we are doing now, is fixing the left number to be 20, and then checking it against every number in the range of 1 to 20 and see if it's divisible. This makes checking if a number is prime a whole lot simpler! Let's try an example where we know it's a prime nubmer, for example 13 (it's not divisisble by any other number than 1 and itself).

In [52]:
left = 13  # <-- changed
right_numbers = range(1, 12)  # <-- changed

for right in numbers: 
    result = left % right
    
    if result is 0:         
        print(True)
        
    else: 
        print(False)
True
False
False
False
False
False
False
False
False

Because we are starting our range from 1, one get one True in the beginning, so we have to start the range from 2 instead to get the right answer. As you can see, I changed the second line so that we scan until 12 which is the last number before 13. Let's put this inside a function as our fifth algo version and make the range start from 2 instead of 1.

In [53]:
def fifth_algo(left, right): 

    right_numbers = range(2, right)   # <-- changed

    for right in right_numbers: # <-- changed
        result = left % right

        if result is 0:         
            print(True)

        else: 
            print(False)

Now things are starting to look good. We could now remove 'left' variable entirely as it comes as an argument from the function, and also instead of having to modify the function for the last number of the range, we also input that as an argument.

In [54]:
fifth_algo(7, 6)
False
False
False
False

That's it, we're prime number checking now! :) Because the result is False for all, we know for sure that our input, in this case 7, is a prime. There is one more very small change we can do using the skill we've already learn to make a nice improvement to what we already have. Instead of requiring the user to input the end of the range, we can automatically compute it as it's always the last number before left. In other words, it's left - 1.

In [55]:
def sixth_algo(left):  # <-- changed

    right_numbers = range(2, left - 1) # <-- changed

    for right in right_numbers:
        result = left % right

        if result is 0:         
            print(True)

        else: 
            print(False)
In [56]:
sixth_algo(9)
False
True
False
False
False
False
In [57]:
sixth_algo(11)
False
False
False
False
False
False
False
False
In [58]:
sixth_algo(19)
False
False
False
False
False
False
False
False
False
False
False
False
False
False
False
False

Things are working real nicely now. But clearly we will later have a problem with larger numbers with this current approach, as if we input 1,000, we will have 1,000 True or False values printed on the screen. To overcome this, we can make a small change to our latest version.

In [59]:
def seventh_algo(left):

    right_numbers = range(2, left - 1)
    output = 0 # <-- changed
    
    for right in right_numbers:
        result = left % right

        if result is 0:         
            output += 1 # <-- changed

        else: 
            output += 0 # <-- changed
            
    return output # <-- changed

What we are doing, is first we declare a variable 'output' with starting value 0. Then instead of printing out True, we silently add 1 to output, and in case of False we add 0. Only in the end we print the value out, with the return statement that is outside of the for loop (note how it's indentation is equal to the for statement, meaning it will be processed only once the for loop has completed its job).

In [60]:
seventh_algo(19)
Out[60]:
0

Nice. Now we can key in much larger numbers, and just get one output.

In [61]:
seventh_algo(127)
Out[61]:
0

Before wrapping up, let's simplify our code slightly and instead of outputting a number, output a True or False statement. True for 'it's a prime' and False for 'it's not a prime'.

In [62]:
def eight_algo(left):

    right_numbers = range(2, left - 1)
    output = 0
    
    for right in right_numbers:
        result = left % right

        if result is 0:         
            output += 1
            
    return output is 0  # <-- changed
In [63]:
eight_algo(19)
Out[63]:
True
In [64]:
eight_algo(127)
Out[64]:
True
In [65]:
eight_algo(12)
Out[65]:
False

Note how we removed the else statements entirely. Because we are doing nothing in the cases where the left number is not divisible by the right number. In other words, whenever the product of the modulus operation is not zero, we do nothing. Therefore it's enough to just have the if statement without the else. This is quite common.

Part 4 Summary

  • Generally speaking computer programs are focused on process automation
  • Loops are a highly effective method for automation
  • With small changes to our code, we can make big improvements in capability
  • Sometimes we can get more done with less code!
  • It's very convinient to store values in to memory
  • Computer memory is nothing like human memory, and also not like a safe deposit box
  • Any value can be stored in to memory
  • Numbers can be automatically generated with range function
  • It's meaningful to learn new concepts by gradually improving things

We've made great progress! Time to wrap up for now, and then in the next part we get in to the real action, looking for prime numbers! With the skills you're learn so far, you're doing a lot of the things the day-to-day of advanced programmers and data scientists is made of.