Due by 11:59pm on Thursday, 10/6

## Instructions

Download hw06.zip. The vitamin problems can be found in the `vitamin` directory and the homework problems can be found in the `problems` directory.

You must run `python3 ok --submit` two times: once inside the `vitamin` directory and once inside the `problems` directory.

Submission: When you are done, submit with `python3 ok --submit`. You may submit more than once before the deadline; only the final submission will be scored. Check that you have successfully submitted your code on okpy.org. See Lab 0 for more instructions on submitting assignments.

Using OK: If you have any questions about using OK, please refer to this guide.

Readings: You might find the following references useful:

# Vitamins

### Question 1: Counter

Define a function `make_counter` that returns a `counter` function, which takes a string and returns the number of times that the function has been called on that string.

``````def make_counter():
"""Return a counter function.

>>> c = make_counter()
>>> c('a')
1
>>> c('a')
2
>>> c('b')
1
>>> c('a')
3
>>> c2 = make_counter()
>>> c2('b')
1
>>> c2('b')
2
>>> c('b') + c2('b')
5
"""
``````

Use OK to test your code:

``python3 ok -q make_counter``

### Question 2: Next Fibonacci

Write a function `make_fib` that returns a function that returns the next Fibonacci number each time it is called. Use a `nonlocal` statement!

``````def make_fib():
"""Returns a function that returns the next Fibonacci number
every time it is called.

>>> fib = make_fib()
>>> fib()
0
>>> fib()
1
>>> fib()
1
>>> fib()
2
>>> fib()
3
>>> fib2 = make_fib()
>>> fib() + sum([fib2() for _ in range(5)])
12
"""
``````

Use OK to test your code:

``python3 ok -q make_fib``

### Question 3: Retirement

Add a `time_to_retire` method to the `Account` class that takes an `amount`. It returns how many years the holder would need to wait in order for the current `balance` to grow to at least `amount`, assuming that the bank adds `balance` times the `interest` rate at the end of every year.

``````class Account:
"""An account has a balance and a holder.

>>> a = Account('John')
>>> a.deposit(10)
10
>>> a.balance
10
>>> a.interest
0.02

>>> a.time_to_retire(10.25) # 10 -> 10.2 -> 10.404
2
>>> a.balance               # balance should not change
10
>>> a.time_to_retire(11)    # 10 -> 10.2 -> ... -> 11.040808032
5
>>> a.time_to_retire(100)
117
"""

interest = 0.02  # A class attribute

def __init__(self, account_holder):
self.holder = account_holder
self.balance = 0

def deposit(self, amount):
self.balance = self.balance + amount
return self.balance

def withdraw(self, amount):
"""Subtract amount from balance if funds are available."""
if amount > self.balance:
return 'Insufficient funds'
self.balance = self.balance - amount
return self.balance

def time_to_retire(self, amount):
"""Return the number of years until balance would grow to amount."""
assert self.balance > 0 and amount > 0 and self.interest > 0
``````

Use OK to test your code:

``python3 ok -q Account``

### Question 4: Free Checking

Implement `FreeChecking`, which is like the `CheckingAccount` from lecture except that it only charges a withdraw fee after 2 free withdrawals. Such a deal! Even unsuccessful withdrawals count against the free quota, but only successful withdrawals actually incur a fee.

``````class FreeChecking(Account):
"""A bank account that charges for withdrawals, but the first two are free!

>>> ch = FreeChecking('Jack')
>>> ch.balance = 20
>>> ch.withdraw(3)    # First one's free
17
>>> ch.withdraw(100)  # And the second
'Insufficient funds'
>>> ch.balance
17
>>> ch.withdraw(3)    # Ok, two free withdrawals is enough
13
>>> ch.withdraw(3)
9
>>> ch2 = FreeChecking('John')
>>> ch2.balance = 10
>>> ch2.withdraw(3) # No fee
7
>>> ch.withdraw(3)  # ch still charges a fee
5
"""
withdraw_fee = 1
free_withdrawals = 2

``````

Use OK to test your code:

``python3 ok -q FreeChecking``

# Homework Questions

## Mobiles

Acknowledgements. This mobile example is based on a classic problem from Structure and Interpretation of Computer Programs, Section 2.2.2.

A mobile is a type of hanging sculpture. A binary mobile consists of two sides. Each side is a rod of a certain length, from which hangs either a weight or another mobile.

We will represent a binary mobile using the data abstractions below, which use the `tree` data abstraction for their representation.

• A `mobile` has a left side (index 0) and a right side (index 1).
• A `side` has a length and a structure, which is either a `mobile` or `weight`.
• A `weight` has a size, which is a positive number.

### Question 5: Weights

Implement the `weight` data abstraction by completing the `weight` constructor, the `size` selector, and the `is_weight` predicate so that a weight is represented using a tree. The `total_weight` example is provided to demonstrate use of the mobile, side, and weight abstractions.

``````def mobile(left, right):
"""Construct a mobile from a left side and a right side."""
return tree(None, [left, right])

def sides(m):
"""Select the sides of a mobile."""
return branches(m)``````
``````def side(length, mobile_or_weight):
"""Construct a side: a length of rod with a mobile or weight at the end."""
return tree(length, [mobile_or_weight])

def length(s):
"""Select the length of a side."""
return root(s)

def end(s):
"""Select the mobile or weight hanging at the end of a side."""
return branches(s)[0]``````
``````def weight(size):
"""Construct a weight of some size."""
assert size > 0

def size(w):
"""Select the size of a weight."""

def is_weight(w):
"""Whether w is a weight, not a mobile."""
``````

Use OK to test your code:

``python3 ok -q total_weight``

### Question 6: Balanced

Implement the `balanced` function, which returns whether `m` is a balanced mobile. A mobile is said to be balanced if the torque applied by its left side is equal to that applied by its right side (that is, if the length of the left rod multiplied by the total weight hanging from that rod is equal to the corresponding product for the right side) and if each of the submobiles hanging off its sides is balanced.

``````def balanced(m):
"""Return whether m is balanced.

>>> t, u, v = examples()
>>> balanced(t)
True
>>> balanced(v)
True
>>> w = mobile(side(3, t), side(2, u))
>>> balanced(w)
False
>>> balanced(mobile(side(1, v), side(1, w)))
False
>>> balanced(mobile(side(1, w), side(1, v)))
False
"""
``````

Use OK to test your code:

``python3 ok -q balanced``

### Question 7: Totals

Implement the `with_totals` function, which takes a `mobile` and returns a tree representation of that same mobile in which the root label of each mobile tree is the total weight of the mobile it represents (instead of `None`).

Note: This function needs to assume that a mobile is represented as a tree.

``````def with_totals(m):
"""Return a mobile with total weights stored as the root of each mobile.

>>> t, _, v = examples()
>>> root(with_totals(t))
3
>>> print(root(t))                           # t should not change
None
>>> root(with_totals(v))
9
>>> [root(end(s)) for s in sides(with_totals(v))]
[3, 6]
>>> [root(end(s)) for s in sides(v)]         # v should not change
[None, None]
"""
``````

Use OK to test your code:

``python3 ok -q with_totals``

## Mutation

### Question 8: Password Protected Account

In lecture, we saw how to use functions to create mutable objects. Here, for example, is the function `make_withdraw` which produces a function that can withdraw money from an account:

``````def make_withdraw(balance):
"""Return a withdraw function with BALANCE as its starting balance.
>>> withdraw = make_withdraw(1000)
>>> withdraw(100)
900
>>> withdraw(100)
800
>>> withdraw(900)
'Insufficient funds'
"""
def withdraw(amount):
nonlocal balance
if amount > balance:
return 'Insufficient funds'
balance = balance - amount
return balance
return withdraw``````

Write a version of the `make_withdraw` function that returns password-protected withdraw functions. That is, `make_withdraw` should take a password argument (a string) in addition to an initial balance. The returned function should take two arguments: an amount to withdraw and a password.

A password-protected `withdraw` function should only process withdrawals that include a password that matches the original. Upon receiving an incorrect password, the function should:

1. Store that incorrect password in a list, and
2. Return the string 'Incorrect password'.

If a withdraw function has been called three times with incorrect passwords `p1`, `p2`, and `p3`, then it is locked. All subsequent calls to the function should return:

``"Your account is locked. Attempts: [<p1>, <p2>, <p3>]"``

The incorrect passwords may be the same or different:

``````def make_withdraw(balance, password):

>>> w = make_withdraw(100, 'hax0r')
>>> w(25, 'hax0r')
75
>>> w(90, 'hax0r')
'Insufficient funds'
>>> w(25, 'hwat')
>>> w(25, 'hax0r')
50
>>> w(75, 'a')
>>> w(10, 'hax0r')
40
>>> w(20, 'n00b')
>>> w(10, 'hax0r')
"Your account is locked. Attempts: ['hwat', 'a', 'n00b']"
>>> w(10, 'l33t')
"Your account is locked. Attempts: ['hwat', 'a', 'n00b']"
"""
``````

Use OK to test your code:

``python3 ok -q make_withdraw``

### Question 9: Joint Account

Suppose that our banking system requires the ability to make joint accounts. Define a function `make_joint` that takes three arguments.

1. A password-protected `withdraw` function,
2. The password with which that `withdraw` function was defined, and
3. A new password that can also access the original account.

The `make_joint` function returns a `withdraw` function that provides additional access to the original account using either the new or old password. Both functions draw down the same balance. Incorrect passwords provided to either function will be stored and cause the functions to be locked after three wrong attempts.

Hint: The solution is short (less than 10 lines) and contains no string literals! The key is to call `withdraw` with the right password and amount, then interpret the result. You may assume that all failed attempts to withdraw will return some string (for incorrect passwords, locked accounts, or insufficient funds), while successful withdrawals will return a number.

Use `type(value) == str` to test if some `value` is a string:

``````def make_joint(withdraw, old_password, new_password):
the balance of withdraw.

>>> w = make_withdraw(100, 'hax0r')
>>> w(25, 'hax0r')
75
>>> make_joint(w, 'my', 'secret')
>>> j = make_joint(w, 'hax0r', 'secret')
>>> w(25, 'secret')
>>> j(25, 'secret')
50
>>> j(25, 'hax0r')
25
>>> j(100, 'secret')
'Insufficient funds'

>>> j2 = make_joint(j, 'secret', 'code')
>>> j2(5, 'code')
20
>>> j2(5, 'secret')
15
>>> j2(5, 'hax0r')
10

>>> j2(5, 'secret')
``python3 ok -q make_joint``