Suppose that Python would not have built-in max()
and min()
function, and neither do you have knowledge of (or are allowed to) use
anything of the chapters after this one. You get the following
assignment:
Write a program that processes two groups of three numbers (you can write the program for fixed numbers, but later on you will add that the user enters these numbers). It adds up the lowest numbers of each of the groups, the middle numbers of each of the groups, and the highest numbers of each of the groups. It then prints these three results.
How do you do this? You can start with something like:
# First initialize variables in group 1 (num11, num12, num13)
# and in group 2 (num21, num22, num23) to some values.
smallest1 = 0
smallest2 = 0
medium1 = 0
medium2 = 0
largest1 = 0
largest2 = 0
if num11 < num12:
if num11 < num13:
smallest1 = num11
else:
smallest1 = num13
elif num12 < num13:
smallest1 = num12
else:
smallest1 = num13
# Test:
print( smallest1 )
# This works to get the smallest from group 1.
# Now do the same for the smallest from group 2.
# Then do something similar for the largest of group 1 and 2.
# Then invent something for taking the middle one.
# Finally, do all the additions and print the results...
You can imagine that with this approach, with nested if
statements
that get repeated six times with different assignments in the branches,
this becomes a huge, unreadable, unmanageable program of which it is
hard to see whether it is correct or not. You have to approach the
problem in a smarter way.
Suppose that you have a function that determines the smallest of three numbers, a function that determines the middle one of three numbers, and a function that determines the largest one of three numbers. Then the program is pretty simple to write! It will be something like:
num11, num12, num13 = 436, 178, 992
num21, num22, num23 = 880, 543, 101
def smallest( n1, n2, n3 ):
return n1 # just return something for now
def middle( n1, n2, n3 ):
return n1 # just return something for now
def largest( n1, n2, n3 ):
return n1 # just return something for now
print( "sum of smallest =", smallest( num11, num12, num13 ) +
smallest( num21, num22, num23 ) )
print( "sum of middle =", middle( num11, num12, num13 ) +
middle( num21, num22, num23 ) )
print( "sum of largest =", largest( num11, num12, num13 ) +
largest( num21, num22, num23 ) )
Note
In the code above, to reduce the size, I used a “multiple assignment” to give the variables
num
\(xx\) their values. You can have multiple variables at the right of the assignment operator, and an equal number of values to the left, and the first value will then go to the first variable, the second value to the second variable, etcetera. I will discuss this in more depth in Chapter 121.
The program above readable, understandable, and can already be tested.
True, the functions smallest()
, middle()
, and largest()
do not
return the correct values yet. While writing the program above, you
might not even have an idea on how to write them. But you probably feel
that they could be written, and you know that you can produce code for
them later, and step by step.
So how do you do smallest()
? Well, as I showed above, doing this with
nested if
statements becomes a bit convoluted and unreadable (really,
don’t look at how I did it and try to write this yourself; it is pretty
hard to keep the three variables in your head while writing such a
nested if
). Can this be approached in a more readable way?
Is it hard to determine the smallest of two numbers? No, that is really easy:
def smallest_of_two( n1, n2 ):
if n1 < n2:
return n1
return n2
By nesting such a function, you can make a smallest()
function that
determines the smallest of three numbers. The same can be done for
largest()
. So the program now becomes:
num11, num12, num13 = 436, 178, 992
num21, num22, num23 = 880, 543, 101
def smallest_of_two( n1, n2 ):
if n1 < n2:
return n1
return n2
def largest_of_two( n1, n2 ):
if n1 > n2:
return n1
return n2
def smallest( n1, n2, n3 ):
return smallest_of_two( smallest_of_two( n1, n2 ), n3 )
def middle( n1, n2, n3 ):
return n1 # just return something for now
def largest( n1, n2, n3 ):
return largest_of_two( largest_of_two( n1, n2 ), n3 )
print( "sum of smallest =", smallest( num11, num12, num13 ) +
smallest( num21, num22, num23 ) )
print( "sum of middle =", middle( num11, num12, num13 ) +
middle( num21, num22, num23 ) )
print( "sum of largest =", largest( num11, num12, num13 ) +
largest( num21, num22, num23 ) )
The program now works as far as smallest numbers and largest numbers are
concerned. To complete the code, a solution must be found for the
middle. What is the middle of three numbers? It is the number that
remains if the smallest and largest are taken out. Can this be
programmed? Here I propose a remove_two_of_three()
function. The
function first removes the smallest from three numbers, then removes the
largest of the remaining two, which is the same as the largest of the
original three. So, for an easy implementation of
remove_two_of_three()
, I also implement functions
remove_one_of_three()
and remove_one_of_two()
.
num11, num12, num13 = 436, 178, 992
num21, num22, num23 = 880, 543, 101
def smallest_of_two( n1, n2 ):
if n1 < n2:
return n1
return n2
def largest_of_two( n1, n2 ):
if n1 > n2:
return n1
return n2
def remove_one_of_three( n1, n2, n3, remove ):
if n1 == remove:
return n2, n3
elif n2 == remove:
return n1, n3
return n1, n2
def remove_one_of_two( n1, n2, remove ):
if n1 == remove:
return n2
return n1
def remove_two_of_three( n1, n2, n3, remove1, remove2 ):
num1, num2 = remove_one_of_three( n1, n2, n3, remove1 )
return remove_one_of_two( num1, num2, remove2 )
def smallest( n1, n2, n3 ):
return smallest_of_two( smallest_of_two( n1, n2 ), n3 )
def middle( n1, n2, n3 ):
return remove_two_of_three( n1, n2, n3,
smallest( n1, n2, n3 ), largest( n1, n2, n3 ) )
def largest( n1, n2, n3 ):
return largest_of_two( largest_of_two( n1, n2 ), n3 )
print( "sum of smallest =", smallest( num11, num12, num13 ) +
smallest( num21, num22, num23 ) )
print( "sum of middle =", middle( num11, num12, num13 ) +
middle( num21, num22, num23 ) )
print( "sum of largest =", largest( num11, num12, num13 ) +
largest( num21, num22, num23 ) )
The program is now finished and it works. It is fairly long, but all the
functions are easy to understand, and it is also easy to understand why
the program works. It is still shorter than the original attempt, with
at least six nested if
statements, would have been, and it is a lot
more readable.
It might be that there are different approaches for this program. With some inventiveness, you might come up with smarter ways to determine smallest, middle, and largest (I am not completely satisfied with my approach for the middle). But the program works, and is understandable, and that is the most important.
You can criticize the approach that I take in this program. For instance, calculation of the smallest of the same three numbers takes place twice: once to determine the smallest, and once to determine the middle. The same holds for the largest. Can this be optimized, so that such a determination takes place only once? Of course it can, for instance by the introduction of two extra variables that keep track of the smallest and largest numbers. But why would I? That would not make the program more readable, and while it would make the program a bit faster, I am talking nanoseconds here. For a program like this, speed is unimportant and completely subject to readability. Let me stress again that while learning programming, solving a problem correctly comes first, immediately followed by solving a problem in a readable and maintainable way. Efficiency comes much later.
What you should learn from this, is that when a program consists of a series of problems that you find hard to solve, you should try to split it into sub-problems or sub-goals, and solve these independently. You can often already introduce functions for sub-problems when you set up the program, and then for the time being fill these function templates with something simple, like returning a constant. You can then at least test the program. Later on, you can start filling in all the function templates that you created.