Solutions: You can find the file with solutions for all questions here.

Tree Recursion

Question 1: Count Change

A set of coins makes change for n if the sum of the values of the coins is n. For example, if you have 1-cent, 2-cent and 4-cent coins, the following sets make change for 7:

  • 7 1-cent coins
  • 5 1-cent, 1 2-cent coins
  • 3 1-cent, 2 2-cent coins
  • 3 1-cent, 1 4-cent coins
  • 1 1-cent, 3 2-cent coins
  • 1 1-cent, 1 2-cent, 1 4-cent coins

Thus, there are 6 ways to make change for 7. Write a function count_change that takes a positive integer n and a list of the coin denominations and returns the number of ways to make change for n using these coins (Hint: You will need to use tree recursion):

def count_change(amount, denominations):
    """Returns the number of ways to make change for amount.

    >>> denominations = [50, 25, 10, 5, 1]
    >>> count_change(7, denominations)
    2
    >>> count_change(100, denominations)
    292
    >>> denominations = [16, 8, 4, 2, 1]
    >>> count_change(7, denominations)
    6
    >>> count_change(10, denominations)
    14
    >>> count_change(20, denominations)
    60
    """
    if amount < 0 or denominations == []:
        return 0
    elif amount == 0:
        return 1
    using_coin = count_change(amount - denominations[0], denominations)
    not_using_coin = count_change(amount, denominations[1:])
    return using_coin + not_using_coin

Use OK to test your code:

python3 ok -q count_change

Dice Rolling

Introduction. Brian and Shreya are playing a dice game. This dice game is special in that players choose the range of one of their dice, as well as one of their opponents. Unfortunately, both Brian and Shreya forgot to bring dice to their game - all they have is their laptop, and they need you to build an abstract object to simulate a die. Given the smallest and largest values of the die, you can construct the die using the dice constructor.

Question 2

In order to create the dice object, create a list of all the values that the dice can take on.

import random
random.seed(42)

def dice(x, y):
    """Construct a die that is a list from x to y inclusive.
    >>> dice(1, 6)
    [1, 2, 3, 4, 5, 6]
    >>> dice(3, 5)
    [3, 4, 5]
    >>> dice(5, 5)
    [5]
    """
    return [i for i in range(x, y+1)]

def smallest(die):
    """Return the lowest value die can take on."""
    return min(die)

def largest(die):
    """Return the largest value die can take on."""
    return max(die)

def str_dice(die):
    """Return a string representation of die.

    >>> str_dice(dice(1, 6))
    'die takes on values from 1 to 6'
    """
    return 'die takes on values from {0} to {1}'.format(smallest(die), largest(die))

Use OK to test your code:

python3 ok -q dice

Question 3

Die rolls are inherently random - before rolling, we do not know what value the die will take on. We need to implement this feature in order to play the game. Python has a nice library that allows us to generate random numbers. You can read more about it here. We will be specifically using random.choice(seq), which generates a random integer from a nonempty sequence seq.

def roll_dice(die, a):
    """Roll the die a times and return an array of the rolled values.
    >>> roll_dice(dice(5, 5), 4)
    [5, 5, 5, 5]
    >>> max(roll_dice(dice(1, 6), 100))
    6
    >>> min(roll_dice(dice(1, 6), 100))
    1
    >>> x = sum(roll_dice(dice(1, 6), 1000))/1000 # Finds the mean of 1000 dice rolls
    >>> 3 <= x <= 4 # Checks if the mean is between 3 and 4
    True
   """
    return [random.choice(die) for _ in range(a)]

Use OK to test your code:

python3 ok -q roll_dice

Question 4

Rolling a six is unfortunate in this game. Neither player wants a 6, yet they cannot avoid it. Figure out how many rolls it takes until a player rolls a 6 with a certain die.

def rolls_until_six(die):
    """Roll the die until you get a 6 and return the number of rolls it took to do so.
    If six is not a the possible values to roll, return a string saying '6 is not a possible value of this die'
    >>> rolls_until_six(dice(1, 5))
    '6 is not a possible value of this die'
    >>> rolls_until_six(dice(6, 6)) # Takes one roll to get 6
    1
    >>> x = sum([rolls_until_six(dice(1, 6)) for _ in range(1000)])/1000 # Repeat 1000 times and average
    >>> 5 <= x <= 7 # Check that it takes between 5 and 7 rolls overall on average
    True
    """
    if not smallest(die) <= 6 <= largest(die):
        return '6 is not a possible value of this die'
    count = 1
    while random.choice(die) != 6:
        count += 1
    return count

Use OK to test your code:

python3 ok -q rolls_until_six

Question 5

The game has a new complication - instead of rolling a single die at a time, players are required to roll multiple dice at a time. Players start off with 2 dice in a cup, but they can add more.

def cup(die_1, die_2):
    """Construct a cup that contains die1 and die2.
    >>> cup(dice(1, 1), dice(1, 2))
    [[1], [1, 2]]
    """
    return [die_1, die_2]

Use OK to test your code:

python3 ok -q cup

Question 6

Add a die to your cup implementation!

def add_to_cup(cup, die):
    """Add die to cup.
    >>> cup1 = cup(dice(1, 1), dice(1, 2))
    >>> add_to_cup(cup1, dice(1, 3))
    [[1], [1, 2], [1, 2, 3]]
    """
    cup += [die]
    return cup

Use OK to test your code:

python3 ok -q add_to_cup

Question 7

When you roll a cup, you roll each dice in the cup once, then return an array of the rolled values.

def roll_cup(cup):
    """Roll every die in the cup and return an array of the rolled values.
    >>> roll_cup(cup(dice(1, 1), dice(2, 2)))
    [1, 2]
    """
    return [roll_dice(die, 1)[0] for die in cup]

Use OK to test your code:

python3 ok -q roll_cup