'Aardvark' < 'Zebra'
'aardvark' < 'Zebra'
True
False
While Loops
for
and while
loops and differentiate between thembreak
and continue
keywords to further control iterative algorithmsfor
and while
loops for a computational problemWill the following print statement get executed?
Yes. Most values can be used in a boolean context. In Python, 0
, 0.0
, None
, empty sequences (e.g. ""
) and a few other values evaluate as False
(often termed “falsy”) and everything else is True
(often termed “truthy”).
In general, I don’t recommend using this “implicit” type conversion as it just increases the chances for difficult-to-find bugs.
Now that we know about implicit booleans, though, we can resolve a common bug. In a previous in-class question, the solution was a == b or a == 5
. Can we simplify that expression as a == b or 5
(i.e., is ==
distributive)? No, that expression is evaluated as a (a == b) or 5
, which is not the same (and will always evaluate to True
as 5 evaluates as True
). What about a == (b or 5)
? No, equality is not distributive. Instead if b
is truthy, the above expression simplifies to a == b
, if not, it simplifies to a == 5
. If a
and b
are both 0, we should get True, but will get False.
Let’s think about a and b
. If a
evaluates to False
do I need to evaluate b
? Similarly if in a or b
a
evaluates to True
do I need to evaluate b
?
No. Python (and many other languages) “short-circuit” the evaluation of logical expressions. Doing so can both improve efficiency (we don’t perform computations we don’t need) and help us manage potentially problematic situations. For example in the following, we only ever execute dangerous_operation
if is_valid(input)
evaluates to True
.
We can also apply relational operators, i.e. <
, ==
, etc., to other types; most notably strings. For example:
That second one doesn’t seem to make much sense… Just as +
has different meaning for strings than integers, the <
a meaning specific to strings. Python compares strings using lexicographic ordering, i.e., it compares the ordering of corresponding characters. The first characters are compared, if equal, then the 2nd characters are compared and so on. If one string is a substring of the other, it is lexicographically less than, e.g.,
>>> "abc" < "abcdef"
True
This is not the same as a case-insensitive alphabetical ordering. When two letters are being compared, that comparison is based on their underlying numeric encoding. In the character encoding used by Python (and lots of other software), all upper case letters are less than lower case letters. Hence “aardvark” is “greater than” “Zebra”. You can access this numerical encoding with the ord
function, e.g.,
Some more examples:
If we wanted to ensure that a string comparison was case insensitive, how could be we do so? Use the upper
or lower
methods to ensure consistent case.
while
loopsWe previously used for
loops to execute a block of code for a specific number of repetitions. What if we don’t know how many iterations are needed? What might be such a situation? Obtaining a valid input from a user. We don’t know how many tries it will take someone to provide a valid input.
This is where we apply while
loops. The general structure of a while
loop is:
The statements in the loop body (i.e. statement1 … statementn) will be executed repeatedly until the boolean expression, the loop conditional, evaluates to False
.
Here is a concrete example. What will this code print?
What is the necessary implication of while
loops? That some statement(s) within the body of the while
loop will change the loop conditional (or otherwise terminate the loop). What happens if that is not the case, e.g.
Hit Ctrl-C (Ctrl and C simultaneously) or hover over the name of the terminal in VS Code
and click the button that looks like a trash can to stop execution. This is called an infinite loop and is a common problem. You will likely need to use Ctrl-C or the stop sign button at some point.
What about the following loop? Will it terminate?
No. Because i
starts at 0 and only gets smaller it will always be less than 10. What about this loop?
Yes. If the value is less than 10, the loop with terminate after some number of iterations. If the value is greater than or equal to 10, the loop won’t execute at all.
In addition to changing the loop conditional we can also explicitly terminate the loop with the break
statement. As its name suggests, break
terminates the loop and begins executing the first statement after the loop. Although break
is most commonly used with while
loops, it can also be used to end for
loops “early” (i.e., only perform a subset of the iterations).
Let’s implement a guessing game in which Python picks a random integer from 1-20 (inclusive), and keeps asking the user to guess the number until they get it right. To help the user, the game should give hints “higher”, or “lower”.
What would be some key elements in such a program?
Check out guessing_game.py which implements one way to make this game. Currently, a boolean variable correct
is used to track if the user answered correctly. But there are other strategies we could use:
break
out of a “while True” loop on a correct answerWe’ll investigate these strategies together which will then be posted here after class.
Additionally, note the use of the input
function for reading user inputs. input
prints its prompt
argument then waits for and returns the string the user typed before hitting Enter/Return. input
returns a str
ing and so we need to convert the result to an int
for use in our game.
Help on built-in function input in module builtins:
input(prompt='', /)
Read a string from standard input. The trailing newline is stripped.
The prompt string, if given, is printed to standard output without a
trailing newline before reading input.
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
On *nix systems, readline is used if available.
while
vs for
loopsOften we can implement the same functionality with both a for
loop and a while
loop. In fact, in Python any for
loop can be readily implemented with a while
loop. The reverse is trickier. There are tricks that would enable us to make a for loop behave like a while loop in some situations, but they are exactly that – tricks. So for our purposes we should think of while loops as a superset of for
loops.
As an example, how could we write a for
loop to print the even numbers from 1-10 inclusive?
And the same with a while
loop?
We can see the clear correspondence between the while
loop and the for
loop. Here we effectively re-implemented the range
within the while
loop by setting the initial value, the end condition and the increment. What are some other ways we could have achieved the same result? One is to iterate through all integers in the range [1,10], but use an if
statement, e.g., if i % 2 == 0:
to identify and print the even values.
There is yet another way to implement this, and another keyword we can use called continue
. When the interpreter sees continue
it will skip the rest of the expressions in the current iteration and skip ahead to the next iteration.
For example, we can produce an equivalent loop to those above using:
for
vs. while
?So when do we use a for
loop and when do we use a while
loop?
We use a for
loop when
As an example, iterating over all the elements in a sequence, e.g. a string, is an example of a situation where the number of iterations is known at the initiation of the loop (number of elements in sequence) and the increment is consistent (increment one element each iteration).
We would use a while
loop in other settings, such as
This an example where “style” matters but there are not necessarily clear “rules”. Often one approach or the other is more appropriate. The right choice will make the code more elegant, easier to reason about (and easier to debug).
while
loops in actionThe for
-loop used in our snake game from last class isn’t a very natural choice, though it works fine for a game that is limited to a certain amount of time. For indefinite games, however, we won’t know how many times to iterate, so this is a better candidate for a while
loop.
Instead, we can replace the for
-loop with a while
loop that uses a condition based on whether the game is over. This “game over” function can also be encapsulated in a function:
The while
loop for running the game can then be:
Many algorithms in computational mathematics need to iterate until some criterion is met which is most naturally achieved with a while
loop.
For example, consider trying to calculate the square root of some number (e.g. if you were writing your own version of math.sqrt
).
In AD 60, Heron described a technique to do this in an iterative way. If we want to calculate the square root of some number \(x\), the idea is to start with a guess \(y_0\) (and \(n = 0\)) and iteratively calculate:
\[ y_{n+1} = \frac{1}{2}\left(y_n + \frac{x}{y_n}\right) \]
until some convergence criterion is met. This criterion can be based on the difference between subsequent guesses, i.e. the difference between \(y_n\) and \(y_{n+1}\). Another option is to measure the difference between \(y_n^2\) and \(x\). When either of these is “small enough”, the algorithm has converged and we’re done.
Let’s implement our own DIY sqrt
function called diy_sqrt
using this technique.
diy_sqrt(x)
TOLERANCE = 1e-12 # scientific notation for 10 ** (-12)
def diy_sqrt(x):
"""
Calculates the square root of some number x using Heron's method.
Args:
x: number to calculate the square root of. MUST BE >= 0.
Returns:
Square-root of x.
"""
y_n = x
while abs(y_n * y_n - x) > TOLERANCE:
y_n = 0.5 * (y_n + x / y_n)
return y_n
Another iterative method for computing the square-root of a number is known as the Bakhshali method and here is a possible implementation:
diy_sqrt(x)
TOLERANCE = 1e-12 # scientific notation for 10 ** (-12)
def diy_sqrt2(x):
"""
Calculates the square root of some number x using the Bakhshali method.
Args:
x: number to calculate the square root of. MUST BE >= 0.
Returns:
Square-root of x.
"""
yn = x
while abs(yn * yn - x) > TOLERANCE:
an = (x - yn * yn) / (2 * yn)
yn += an
yn -= an * an / (2 * yn)
return yn