Below are exercises from chapter 5 of How to Think Like a Computer Scientist: Learning with Python 3 The bits in italics are from the book; the code is my solutions.
Assume the days of the week are numbered 0,1,2,3,4,5,6 from Sunday to Saturday. Write a function which is given the day number, and it returns the day name (a string).
Most likely the book is looking for something like this:
def main():
play_again = "Y"
while play_again == "Y":
x = int(input("Give me a number representing a weekday.\n"))
print("That's for ", weekday(x))
play_again = input("Want to play again? Y/n \n")
def weekday(number):
"""takes an integer (0, 1, 2, 3, 4, 5, or 6) representing a
day of the week, where 0 is Sunday, 1 in Monday ... 6 is Saturday.
Returns the name of the day as a string."""
if number == 0:
return "Sunday"
elif number == 1:
return "Monday"
elif number == 2:
return "Tuesday"
elif number == 3:
return "Wednesday"
elif number == 4:
return "Thursday"
elif number == 5:
return "Friday"
elif number == 6:
return "Saturday"
else:
print("Something seems to have gone wrong in the weekday function")
print("when it was passed a value of ", number)
main()
But this is the perfect situation to use a dictionary, not so much for performance gains as because it’s less ugly. So we can revise to this:
# Exercise 5.14.1. of _How to Think Like a Computer Scientist:
# Learning with Python 3_
weekdays = {
0: "Sunday",
1: "Monday",
2: "Tuesday",
3: "Wednesday",
4: "Thursday",
5: "Friday",
6: "Saturday"
}
while True:
x = int(input("Give me a number representing a weekday.\n"))
print("That's for ", weekdays[x])
if input("Want to play again? Y/n \n") != 'Y':
break
One you start shortening code, it can get hard to quit. Let’s take out the comments and some whitespace, and compress the dictionary.
weekdays = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday",
4: "Thursday", 5: "Friday", 6: "Saturday"}
while True:
x = int(input("Give me a number representing a weekday.\n"))
print("That's for ", weekdays[x])
if input("Want to play again? Y/n \n") != 'Y':
break
Now, let’s ignore the rule about not writing lines of more than 80 characters.
weekdays = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday"}
while True:
x = int(input("Give me a number representing a weekday.\n"))
print("That's for ", weekdays[x])
if input("Want to play again? Y/n \n") != 'Y':
break
And we can get away with jamming the “break” statement onto the previous line.
weekdays = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday"}
while True:
x = int(input("Give me a number representing a weekday.\n"))
print("That's for ", weekdays[x])
if input("Want to play again? Y/n \n") != 'Y': break
The thing we do with x
, where we store an input in it and then look it up in
the dictionary, is helpful to human readers. Let’s not be helpful.
weekdays = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday"}
while True:
print("That's for ", weekdays[int(input("Give me a number representing a weekday.\n"))])
if input("Want to play again? Y/n \n") != 'Y': break
Now let’s move the “Want to play?” question into the same line as the While statement.
weekdays = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday"}
while input('Wanna play again?') == 'Y':
print("That's for ", weekdays[int(input("Give me a number representing a weekday.\n"))])
We can also move the print
statement onto the same line as the While
statement.
weekdays = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday"}
while input('Wanna play again?') == 'Y': print("That's for ", weekdays[int(input("Give me a number representing a weekday.\n"))])
And finally, what we’re doing with the variable weekdays
, we could just do with composition.
while input('Wanna play again?') == 'Y': print("That's for ", {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", 4: "Thursday", 5: "Friday", 6: "Saturday"}[int(input("Give me a number representing a weekday.\n"))])
I’m not yet at the point where I can write zero-line code, so we’ll have to stop there.
You go on a wonderful holiday (perhaps to jail, if you don’t like happy exercises) leaving on day number 3 (a Wednesday). You return home after 137 sleeps. Write a general version of the program which asks for the starting day number, and the length of your stay, and it will tell you the name of day of the week you will return on.
This one is basically just some modular math and some inputs and outputs.
# Dictionary to convert numbers to weeks
weekdays = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", \
4: "Thursday", 5: "Friday", 6: "Saturday"}
# Prompt user, and tell them what day of the week they gave.
print("Give me a number from 0 to 6, representing the day you went to ", \
"your destination, where 0 represents Sunday, 1 represents " \
"Monday, etc. \n")
x = int(input("What is the number? \n"))
print("That's a {}.".format(weekdays[x]))
y = int(input("How many nights did you sleep at your destination? \n"))
# Modular math to find ending day of the week.
z = (x + y) % 7
print("You left your destination on a {}.".format(weekdays[z]))
Write a function which is given an exam mark, and it returns a string — the grade for that mark — according to this scheme:
The square and round brackets denote closed and open intervals. A closed interval includes the number, and open interval excludes it. So 39.99999 gets grade F3, but 40 gets grade F2. Assume
xs = [83, 75, 74.9, 70, 69.9, 65, 60, 59.9, 55, 50,
49.9, 45, 44.9, 40, 39.9, 2, 0]
Test your function by printing the mark and the grade for all the elements in this list.
Notice that the grades are arranged, except for F3, as a series of thresholds. That is, if your score is greater than or equal to 75, you’ve got a “First”. You’re done evaluating your score. If not, then you check the next threshold, 70, to see if you have a Second. And so on. If you fail all the thresholds down to 40, then you get an F3.
So we can think of each threshold as “paired” with a grade, and the thresholds need to be tested in the order 75, 70, 60, 50, 45, 40. We run through the thresholds in descending order, and as soon as one is met, we return the grade. If none are met, we return “F3”.
def grade(mark):
"""Takes a mark and returns a grade using the scheme in
_How to Think Like a Computer Scientist: Learning with Python 3_,
exercise 5.14.6"""
# These are the thresholds to make the various grades.
mark_scheme = [(75, "First"), (70, "Upper Second"), (60, "Second"),
(50, "Third"), (45, "F1 Supp"), (40, "FS")]
# Check the thresholds in descending order and return the highest
# threshold met.
for threshold in mark_scheme:
if mark >= threshold[0]:
return threshold[1]
# If no threshold is met, we return "F3"
return "F3"
# Marks to grade
xs = [83, 75, 74.9, 70, 69.9, 65, 60, 59.9, 55, 50,
49.9, 45, 44.9, 40, 39.9, 2, 0]
# Grade the marks and print them with their results.
for x in xs:
print(x, grade(x))
Modify the turtle bar chart program so that the pen is up for the small gaps between each bar.
In this chapter, the textbook shows us how to write a particular program, which is below. (I had to add line 15 because the program assumed use of the module “turtle” without importing it).
def draw_bar(t, height):
""" Get turtle t to draw one bar, of height. """
t.begin_fill() # Added this line
t.left(90)
t.forward(height)
t.write(" "+ str(height))
t.right(90)
t.forward(40)
t.right(90)
t.forward(height)
t.left(90)
t.end_fill() # Added this line
t.forward(10)
import turtle
wn = turtle.Screen() # Set up the window and its attributes
wn.bgcolor("lightgreen")
tess = turtle.Turtle() # Create tess and set some attributes
tess.color("blue", "red")
tess.pensize(3)
xs = [48,117,200,240,160,260,220]
for a in xs:
draw_bar(tess, a)
wn.mainloop()
That program outputs something like this:
If you know about turtles and walk through the draw_bar function
, you can see
that line 13 is where our turtle, using its blue pen, draws the little section
of blue line that clutters up the graph. So we just need to lift the pen —
t.penup()
— before t.forward(10)
, and then put the pen back down —
t.pendown()
— afterward.
So now the code looks like this:
"""This is basically copied straight from chapter 5 of _How to Think Like a
Computer Scientist: Learning with Python 3_"""
def draw_bar(t, height):
""" Get turtle t to draw one bar, of height. """
t.begin_fill() # Added this line
t.left(90)
t.forward(height)
t.write(" "+ str(height))
t.right(90)
t.forward(40)
t.right(90)
t.forward(height)
t.left(90)
t.end_fill() # Added this line
t.penup()
t.forward(10)
t.pendown()
import turtle
wn = turtle.Screen() # Set up the window and its attributes
wn.bgcolor("lightgreen")
tess = turtle.Turtle() # Create tess and set some attributes
tess.color("blue", "red")
tess.pensize(3)
xs = [48,117,200,240,160,260,220]
for a in xs:
draw_bar(tess, a)
wn.mainloop()
And the program draws this:
Modify the turtle bar chart program so that the bar for any value of 200 or more is filled with red, values between [100 and 200) are filled with yellow, and bars representing values less than 100 are filled with green.
To do this we just need to add some lines to the beginning of draw_bar
,
setting fill-color based on the value of the height
variable.
if height >= 200:
t.color("blue", "red")
elif height >= 100:
t.color("blue", "yellow")
elif height < 100:
t.color("blue", "green")
In the turtle bar chart program, what do you expect to happen if one or more of the data values in the list is negative? Try it out. Change the program so that when it prints the text value for the negative bars, it puts the text below the bottom of the bar.
If we change a couple of the values to negative, say, xs = [48,117,200,-240,160,-260,220]
, we get:
The problem is where the numbers are on the negative bars. We can write in the
numbers in a better spot. We can replace the t.write(" "+ str(height))
bit
with an if
… else
structure. If height
is negative, then we need to do a
little repositioning. Otherwise, we carry on as usual.
if height <0:
t.penup() # So we don't leave messy lines
t.backward(17) # Moves us down into position
t.write(" "+ str(height)) # Write our value
t.forward(17) # retrace our steps
t.pendown() # Pen down to continue drawing
else: # Or, if height is positive,
t.write(" "+ str(height)) # it's business as usual.
Now the whole code looks like this:
"""This is basically copied straight from chapter 5 of _How to Think Like a
Computer Scientist: Learning with Python 3_, and then I modified it."""
def draw_bar(t, height):
""" Get turtle t to draw one bar, of height. Fills bar with color
as specified in _How to Think Like a Computer Scientist:
Learning with Python 3_, chapter 5, exercise 14.8."""
# Set fill-color
if height >= 200:
t.color("blue", "red")
elif height >= 100:
t.color("blue", "yellow")
elif height < 100:
t.color("blue", "green")
t.begin_fill() # Turn fill on before drawing.
t.left(90) # Turn to face upward
t.forward(height) # Draw line upward (if height is positive)
# Write caption for our bar
if height <0: # If height is negative,
t.penup() # we pick up pen to stop drawing for a bit,
t.backward(17) # move downward to an appropriate spot,
t.write(" "+ str(height)) # write our caption,
t.forward(17) # move back up to resume,
t.pendown() # and put our pen back down to continue.
else: # Otherwise, height is positive, and
t.write(" "+ str(height)) # we can just go ahead and caption.
t.right(90) # Turn to produce "top" (for positive values) of bar.
t.forward(40) # Draw the top.
t.right(90) # Turn to draw other wise of bar.
t.forward(height) # Draw other side of bar.
t.left(90) # Turn back to original position.
t.end_fill() # Fill in the bar.
t.penup() # Stop drawing.
t.forward(10) # Move into position for the next bar.
t.pendown() # And get ready to draw again.
import turtle
wn = turtle.Screen() # Set up the window and its attributes
wn.bgcolor("lightgreen")
tess = turtle.Turtle() # Create a turtle and set some attributes
tess.color("blue", "red")
tess.pensize(3)
xs = [48,117,200,-240,160,-260,220] # Our data to graph
for a in xs: # Graph the bars in order.
draw_bar(tess, a)
wn.mainloop() # Hold screen open till user is done.
Our draw_bar
function is getting a little busy, so we could spin off the part
that writes the caption into its own function height_caption(t, height)
.
Likewise, we can spin off the code that sets the fill color. So now we get
this:
def draw_bar(t, height):
""" Get turtle t to draw one bar, of height. Fills bar with color
as specified in _How to Think Like a Computer Scientist:
Learning with Python 3_, chapter 5, exercise 14.8."""
set_fill_color(t, height)
t.begin_fill() # Turn fill on before drawing.
t.left(90) # Turn to face upward
t.forward(height) # Draw line upward (if height is positive)
height_caption(t, height) # Write height caption for our bar
t.right(90) # Turn to produce "top" (for positive values).
t.forward(40) # Draw the "top".
t.right(90) # Turn to draw other side of bar.
t.forward(height) # Draw other side of bar.
t.left(90) # Turn back to original position.
t.end_fill() # Fill in the bar.
t.penup() # Stop drawing.
t.forward(10) # Move into position for the next bar.
t.pendown() # And get ready to draw again.
def height_caption(t, height):
"""When called at the right time, this writes a caption for our
bar, displaying its height. If the bar represents a positive number, it
writes the caption above the bar. For a negative number, below the bar."""
if height <0: # If height is negative,
t.penup() # we pick up pen to stop drawing for a bit,
t.backward(17) # move downward to an appropriate spot,
t.write(" "+ str(height)) # write our caption,
t.forward(17) # move back up to resume,
t.pendown() # and put our pen back down to continue.
else: # Otherwise, height is positive, and
t.write(" "+ str(height)) # we can just go ahead and caption.
def set_fill_color(t, height):
"""Sets fill-color of a turtle based on the scheme
laid out in exercise 5.14.9 of _How to Think Like a Computer
Scientist: Learning with Python 3_"""
if height >= 200:
t.color("blue", "red")
elif height >= 100:
t.color("blue", "yellow")
elif height < 100:
t.color("blue", "green")
import turtle
wn = turtle.Screen() # Set up the window and its attributes
wn.bgcolor("lightgreen")
tess = turtle.Turtle() # Create a turtle and set some attributes
tess.color("blue", "red")
tess.pensize(3)
xs = [48,117,200,-240,160,-260,220] # Our data to graph
for a in xs: # Graph the bars in order.
draw_bar(tess, a)
wn.mainloop() # Hold screen open till user is done.
Write a function find_hypot
which, given the length of two sides of a
right-angled triangle, returns the length of the hypotenuse. (Hint: x ** 0.5
will return the square root.)
The famous formula
gives the relationship between the three sides of a right triangle, in which c is the hypotenuse. This can be rearranged to
Then it's all a simple matter of rewriting our math in Python:
def find_hypot(a, b):
return (a**2 + b**2) ** 0.5
Write a function is_rightangled
which, given the length of three sides of
a triangle, will determine whether the triangle is right-angled. Assume that
the third argument to the function is always the longest side. It will return
True
if the triangle is right-angled, or False
otherwise.
Hint: Floating point arithmetic is not always exactly accurate, so it is not
safe to test floating point numbers for equality. If a good programmer wants to
know whether x
is equal or close enough to y
, they would probably code
it up as:
if abs(x-y) < 0.000001: # If x is approximately equal to y
...
Here’s an answer:
def is_rightangled(a, b, c):
return abs(a**2 + b**2 - c**2) < 0.000001
But the problem here is that this algorithm is “stricter” when dealing with larger triangles. For example, suppose we test our algorithm with
print(is_rightangled(1, 1, 1.4142135),
is_rightangled(10, 10, 14.142135))
It prints True False
, which seems odd given that 10, 10, 14.142135 has
exactly the same angles as 1, 1, 1.4142135. A test of right-angled-ness that
winds up being sensitive to triangle size is a problem. And as the triangles
get bigger, so does the sensitivity problem.
This sensitivity problem grows to the point of absurdity with very small and very large triangles. Consider the following:
print(is_rightangled(0.0001, 0.0001, 0.0009),
is_rightangled(10**100, 10**100, 1.4142135623730950488016887242096980785696718753769480731766797379907324784621*10**100))
This prints True False
. In other words, we have convinced the computer that a
tiny “right” triangle can have a hypotenuse nine times longer than each of its
legs. On the other hand, it rejects a triangle where the hypotenuse is correct
to 77 significant digits. To measure the size of the observable universe so
accurately that your measurement is off by no more than the width of an atom
requires only 28 significant digits.
This is absurd and useless, but it was the first solution that popped into my head. So, knowing what we know now, let’s try again:
def is_rightangled(a, b, c):
square_of_legs = a**2 + b**2
square_of_hypotenuse = c**2
ratio = square_of_hypotenuse / square_of_legs
ratio_error = abs(ratio - 1)
return ratio_error < 0.000001
That behaves better. Now, we can quibble over whether the function is strict enough, but that’s a practical question, and we can adjust our function depending on how strict we need to be for whatever application we’re building.
Extend the above program so that the sides can be given to the function in any order.
def is_rightangled(a, b, c):
square_of_hypotenuse = (max(a, b, c))**2
square_of_legs = a**2 + b**2 + c**2 - square_of_hypotenuse
ratio = square_of_hypotenuse / square_of_legs
ratio_error = abs(ratio - 1)
return ratio_error < 0.000001
This is an algebraically correct solution, relying on the fact that a^2 + b^2 + c^2 – c^2 = a^2 + b^2, and many readers will be able to figure out why it works if they think about it hard enough or if it is documented well enough. But there’s a potential for confusion here, and we can do better, with a clearer logic.
def is_rightangled(a, b, c):
sides_list = [a, b, c]
sides_list.sort()
short_leg = sides_list[0]
long_leg = sides_list[1]
hypotenuse = sides_list[2]
legs_squared = short_leg**2 + long_leg**2
hypotenuse_squared = hypotenuse**2
ratio = hypotenuse_squared / legs_squared
ratio_error = abs(1 - ratio)
return ratio_error < 0.000001
This page is released under the GNU Free Documentation License, any version 1.3 or later produced by the Free Software Foundation. I am grateful to the authors of the original textbook for releasing it that way: Peter Wentworth, Jeffrey Elkner, Allen B. Downey, and Chris Meyers.