topics for the day:
range
(…)Consider the following function:
def mystery(m):
n = 0
d = 1
for _ in range(m):
if m % d == 0:
n = n + 1
d = d + 1
return n
What would be the result of evaluating these expressions:
>>> mystery(4)
answer:
>>> mystery(5)
answer:
Briefly(!), what does the function compute?
answer: Returns the number of divisors of
m
. (Equivalent: Returns the number of factors of
m
.)
range
revisitedwe can use a variable in a for ... in range(...):
for i in range(5):
...
is equivalent to:
i = 0
for _ in range(5):
...
i = i + 1
In other words, i
takes on the values from 0 up to but
not including 5.
We can also tell the range to begin somewhere other than 0:
for i in range(start, stop):
...
is equivalent to:
i = start
for _ in range(stop - start): # start - start is how many times to repeat the loop
...
i = i + 1
Remember range(start, stop)
means begin at
start
, go up by one each time, but for the loop to end just
before we get to stop
:
>>> for x in range(3, 7):
print(x)
3
4
5
6
So here is the quiz code renamed:
def count_factors(m):
n = 0
d = 1
for _ in range(m):
if m % d == 0:
n = n + 1
d = d + 1
return n
This leverages range variables and start/stop version or range:
def count_factors(m):
n = 2 # because 1 and m will always be factors
for d in range(2, m): # start at 2, go up to, but do not include m itself
if m % d == 0:
n = n + 1
return n
This corrects above so that it works when m
is 1:
def count_factors(m):
n = 1 # because 1 and m will always be factors
for d in range(2, m+1): # start at 2, go up to and *include* m itself
if m % d == 0:
n = n + 1
return n
if
statements to choose between more than two optionsdef calculator(x, y, op):
if op == '+':
z = x + y
else:
if op == '-':
z = x - y
else: # assuming anything else for op means "multiplication"
z = x * y
return z
In this way we can select from one of three (or more) cases. It
assumes that op
is either '+'
,
'-'
or '*'
but it behaves so that anything
other than '+'
or '-'
leads to
multiplication.
Here is a further nested version to handle a fourth and final “error”
case; it assumes op
is '+'
, '*'
,
'-'
, but, if not, it returns -1
to signal an
error. (In class, we discussed how the problem with using a string
result like 'error'
instead of a number such as
-1
is that it can lead to confusion over the type of the
variable z
- we would like to abide by the idea that
within a single function a variable is assigned one and only
one type. But returning -1
or any number is a bad idea.
Later in the semester - time permitting - we will say a superior
approach to error handling.)
def calculator(x, y, op):
if op == '+':
z = x + y
else:
if op == '-':
z = x - y
else:
if op == '*':
z = x * y
else: # if not any of +, - or * then indicate an error
z = -1
return z
With each additional case we wish to add, we need to indent further. That cascading indentation is sometimes referred to as “the pyramid of doom” - it makes code quite difficult to read! Luckily, there is a better way.
Before we get to it, let’s consider what happens if we just use
separate if
statements:
def calculator(x, y, op):
if op == '+':
z = x + y
if op == '-':
z = x - y
if op == '*':
z = x * y
else: # if not any of +, - or * then indicate an error
z = -1
return z
This looks cleaner - no pyramid of doom! - and it is fairly easy for
the reader of the code to get the gist. However, it is wrong!
If op
is anything other than '*'
then the
final else
branch is executed and the result is
-1:
>>> calculator(9, 4, '*') # this is fine!
36
>>> calculator(9, 4, '+') # watch out!
-1
if
statementInstead, we introduce the elif
reserved word that is
used to make a multiway conditional statement:
def calculator(x, y, op):
if op == '+':
z = x + y
elif op == '-':
z = x - y
elif op == '*':
z = x * y
else: # if not any of +, - or * then indicate an error
z = -1
return z
This has the benefit of making the code easy to read (each case is at
the same level of indentation), is correct (vastly more important!), and
(since we are impatient computer scientists), once one of the
if
/elif
test expressions evaluates to
True
and its corresponding branch is executed, the program
skips to just past the last elif
/else
clause
of the multiway branch.
and
, or,
not`Suppose we wish to write a function that takes a string as input and returns a similar string except all but the uppercase letters filtered out, like this:
>>> uppers_only('Computers Only Offer Loops!')
'COOL'
Here is a way to do that with what we have studied so far:
def uppers_only(s):
t = ''
for c in s:
if ord(c) >= ord('A'):
if ord(c) <= ord('Z'):
t = t + c
return t
Makes sense. But seems complicated. The nested if is unsatisfying
since it we are trying to convey that a character’s ASCII code is in a
range (any of the codes that correspond to uppercase letters). Not
surprisingly we can do better, by using a Boolean
connective in this case and
:
ord(c) >= ord ('A') and ord(c) <= ord('Z')
Before we rewrite our function, let’s step back and introduce the three standard Boolean connectives in Python. (Bear in mind that in Python they are conveniently named, not so in many other PLs.)
We have already seen the “not equal” operator as in:
<exp> != <exp>
that is really shorthand for:
not (<exp> == <exp>) # parentheses not necessary - included here for clarity
not
is a reserved word in Python that operates on an
expression to its right that is expected have been evaluated to a
Boolean value and then it inverts the value:
>>> not False
True
>>> not True
false
and
is a reserved word in Python that operates on two
Boolean expressions, one to its left and one to its right. (So
and
is much like binary arithmetic operators, but it takes
two Booleans and produces a Boolean.) Here is the “truth table” for
and
:
>>> False and False
False
>>> False and True
False
>>> True and False
False
>>> True and True
True
In other words (and not surprisingly!) the “and” of two Boolean values is true only if both its operands are true.
or
behaves comparably: the “or” of two Boolean values is
true if either of its operands are true:
>>> False or False
False
>>> False or True
True
>>> True or False
True
>>> True or True
True
We can use and
to encode the idea of testing if a number
is within a range of values:
def between(low, x, high):
b = low <= x and x < high
# Python allows low <= x < high <--- BAD IDEA! do not use in our class!
return b
earlier we used ASCII codes to check if one character is “less than” another:
def char_lt(c, d):
b = ord(c) < ord(d)
return b
Python does that automatically:
'x' < 'y'
is translated (“under the hood”) to:
ord('x') < ord ('y')
so we can use <
, <=
,
>
, >=
directly on characters (and, a
little later, we will see that we can use them on strings more
generally):
def is_upper_char(ch):
b = len(ch) == 1 and 'A' <= ch and ch <= 'Z'
return b
Observe that we check that ch
is a string of length
1.