Think you are ready to use Booleans and Conditionals? Try it yourself and find out.
To get started, run the setup code below before writing your own code (and if you leave this notebook and come back later, don't forget to run the setup code again).
from learntools.core import binder; binder.bind(globals())
from learntools.python.ex3 import *
print('Setup complete.')
Setup complete.
# Your code goes here. Define a function called 'sign'
def sign(n: float):
if n == 0: return 0
return 1 if n > 0 else -1
# Check your answer
q1.check()
Correct
q1.solution()
Solution:
def sign(x):
if x > 0:
return 1
elif x < 0:
return -1
else:
return 0
We've decided to add "logging" to our to_smash
function from the previous exercise.
def to_smash(total_candies):
"""Return the number of leftover candies that must be smashed after distributing
the given number of candies evenly between 3 friends.
>>> to_smash(91)
1
"""
print("Splitting", total_candies, "candies")
return total_candies % 3
to_smash(91)
Splitting 91 candies
1
What happens if we call it with total_candies = 1
?
to_smash(1)
Splitting 1 candies
1
That isn't great grammar!
Modify the definition in the cell below to correct the grammar of our print statement. (If there's only one candy, we should use the singular "candy" instead of the plural "candies")
def to_smash(total_candies):
"""Return the number of leftover candies that must be smashed after distributing
the given number of candies evenly between 3 friends.
>>> to_smash(91)
1
"""
print("Splitting", total_candies, "candies" if total_candies > 1 else "candy")
return total_candies % 3
to_smash(91)
to_smash(1)
Splitting 91 candies Splitting 1 candy
1
# Check your answer (Run this code cell to receive credit!)
q2.solution()
Solution: A straightforward (and totally fine) solution is to replace the original print
call with:
if total_candies == 1:
print("Splitting 1 candy")
else:
print("Splitting", total_candies, "candies")
Here's a slightly more succinct solution using a conditional expression:
print("Splitting", total_candies, "candy" if total_candies == 1 else "candies")
In the main lesson we talked about deciding whether we're prepared for the weather. I said that I'm safe from today's weather if...
The function below uses our first attempt at turning this logic into a Python expression. I claimed that there was a bug in that code. Can you find it?
To prove that prepared_for_weather
is buggy, come up with a set of inputs where either:
False
(but should have returned True
), orTrue
(but should have returned False
).To get credit for completing this question, your code should return a Correct result.
def prepared_for_weather(have_umbrella, rain_level, have_hood, is_workday):
# Don't change this code. Our goal is just to find the bug, not fix it!
return have_umbrella or rain_level < 5 and have_hood or not rain_level > 0 and is_workday
# Change the values of these inputs so they represent a case where prepared_for_weather
# returns the wrong answer.
have_umbrella = False
rain_level = 0
have_hood = False
is_workday = False
# Check what the function returns given the current values of the variables above
actual = prepared_for_weather(have_umbrella, rain_level, have_hood, is_workday)
print(actual)
# Check your answer
q3.check()
False
Correct:
One example of a failing test case is:
have_umbrella = False
rain_level = 0.0
have_hood = False
is_workday = False
Clearly we're prepared for the weather in this case. It's not raining. Not only that, it's not a workday, so we don't even need to leave the house! But our function will return False on these inputs.
The key problem is that Python implictly parenthesizes the last part as:
(not (rain_level > 0)) and is_workday
Whereas what we were trying to express would look more like:
not (rain_level > 0 and is_workday)
#q3.hint()
#q3.solution()
Solution: One example of a failing test case is:
have_umbrella = False
rain_level = 0.0
have_hood = False
is_workday = False
Clearly we're prepared for the weather in this case. It's not raining. Not only that, it's not a workday, so we don't even need to leave the house! But our function will return False on these inputs.
The key problem is that Python implictly parenthesizes the last part as:
(not (rain_level > 0)) and is_workday
Whereas what we were trying to express would look more like:
not (rain_level > 0 and is_workday)
The function is_negative
below is implemented correctly - it returns True if the given number is negative and False otherwise.
However, it's more verbose than it needs to be. We can actually reduce the number of lines of code in this function by 75% while keeping the same behaviour.
See if you can come up with an equivalent body that uses just one line of code, and put it in the function concise_is_negative
. (HINT: you don't even need Python's ternary syntax)
def is_negative(number):
if number < 0:
return True
else:
return False
def concise_is_negative(number):
return number < 0
# Check your answer
q4.check()
Correct
#q4.hint()
#q4.solution()
The boolean variables ketchup
, mustard
and onion
represent whether a customer wants a particular topping on their hot dog. We want to implement a number of boolean functions that correspond to some yes-or-no questions about the customer's order. For example:
def onionless(ketchup, mustard, onion):
"""Return whether the customer doesn't want onions.
"""
return not onion
For each of the remaining functions, fill in the body to match the English description in the docstring.
def wants_all_toppings(ketchup, mustard, onion):
"""Return whether the customer wants "the works" (all 3 toppings)
"""
return ketchup and mustard and onion
# Check your answer
q5.a.check()
Correct
#q5.a.hint()
q5.a.solution()
Solution:
return ketchup and mustard and onion
def wants_plain_hotdog(ketchup, mustard, onion):
"""Return whether the customer wants a plain hot dog with no toppings.
"""
return not (ketchup and mustard and onion)
# Check your answer
q5.b.check()
Incorrect: Expected return value of False
given ketchup=False
, mustard=True
, onion=True
, but got True
instead.
#q5.b.hint()
q5.b.solution()
Solution: One solution looks like:
return not ketchup and not mustard and not onion
We can also "factor out" the nots to get:
return not (ketchup or mustard or onion)
def exactly_one_sauce(ketchup, mustard, onion):
"""Return whether the customer wants either ketchup or mustard, but not both.
(You may be familiar with this operation under the name "exclusive or")
"""
return (ketchup and not mustard) or (mustard and not ketchup)
# Check your answer
q5.c.check()
Correct
#q5.c.hint()
q5.c.solution()
Solution:
return (ketchup and not mustard) or (mustard and not ketchup)
We’ve seen that calling bool()
on an integer returns False
if it’s equal to 0 and True
otherwise. What happens if we call int()
on a bool? Try it out in the notebook cell below.
Can you take advantage of this to write a succinct function that corresponds to the English sentence "does the customer want exactly one topping?"?
def exactly_one_topping(ketchup, mustard, onion):
"""Return whether the customer wants exactly one of the three available toppings
on their hot dog.
"""
number_ketchup, number_mustard, number_onion = int(ketchup), int(mustard), int(onion)
total = number_ketchup + number_mustard + number_onion
return total == 1
# Check your answer
q6.check()
Correct:
This condition would be pretty complicated to express using just and
, or
and not
, but using boolean-to-integer conversion gives us this short solution:
return (int(ketchup) + int(mustard) + int(onion)) == 1
Fun fact: we don't technically need to call int
on the arguments. Just by doing addition with booleans, Python implicitly does the integer conversion. So we could also write...
return (ketchup + mustard + onion) == 1
#q6.hint()
q6.solution()
Solution: This condition would be pretty complicated to express using just and
, or
and not
, but using boolean-to-integer conversion gives us this short solution:
return (int(ketchup) + int(mustard) + int(onion)) == 1
Fun fact: we don't technically need to call int
on the arguments. Just by doing addition with booleans, Python implicitly does the integer conversion. So we could also write...
return (ketchup + mustard + onion) == 1
In this problem we'll be working with a simplified version of blackjack (aka twenty-one). In this version there is one player (who you'll control) and a dealer. Play proceeds as follows:
When calculating the sum of cards, Jack, Queen, and King count for 10. Aces can count as 1 or 11 (when referring to a player's "total" above, we mean the largest total that can be made without exceeding 21. So e.g. A+8 = 19, A+8+8 = 17)
For this problem, you'll write a function representing the player's decision-making strategy in this game. We've provided a very unintelligent implementation below:
def should_hit(dealer_total, player_total, player_low_aces, player_high_aces):
"""Return True if the player should hit (request another card) given the current game
state, or False if the player should stay.
When calculating a hand's total value, we count aces as "high" (with value 11) if doing so
doesn't bring the total above 21, otherwise we count them as low (with value 1).
For example, if the player's hand is {A, A, A, 7}, we will count it as 11 + 1 + 1 + 7,
and therefore set player_total=20, player_low_aces=2, player_high_aces=1.
"""
return False
This very conservative agent always sticks with the hand of two cards that they're dealt.
We'll be simulating games between your player agent and our own dealer agent by calling your function.
Try running the function below to see an example of a simulated game:
q7.simulate_one_game()
Player starts with 7 and 5 (total = 12) Dealer starts with 10 __Player's turn__ Player stays __Dealer's turn__ Dealer hits and receives 4. (total = 14) Dealer hits and receives J. (total = 24) Dealer busts! Player wins.
The real test of your agent's mettle is their average win rate over many games. Try calling the function below to simulate 50000 games of blackjack (it may take a couple seconds):
q7.simulate(n_games=50000)
Player won 19080 out of 50000 games (win rate = 38.2%)
Our dumb agent that completely ignores the game state still manages to win shockingly often!
Try adding some more smarts to the should_hit
function and see how it affects the results.
def should_hit(dealer_total, player_total, player_low_aces, player_high_aces):
"""Return True if the player should hit (request another card) given the current game
state, or False if the player should stay.
When calculating a hand's total value, we count aces as "high" (with value 11) if doing so
doesn't bring the total above 21, otherwise we count them as low (with value 1).
For example, if the player's hand is {A, A, A, 7}, we will count it as 11 + 1 + 1 + 7,
and therefore set player_total=20, player_low_aces=2, player_high_aces=1.
"""
return True if player_total <= 13 else False
q7.simulate(n_games=50000)
Player won 19043 out of 50000 games (win rate = 38.1%)
Learn about lists and tuples to handle multiple items of data in a systematic way.
Have questions or comments? Visit the Learn Discussion forum to chat with other Learners.