# Homework 6 hw06.zip

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

## Instructions

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:

## Object Oriented Programming

### Q1: 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``

### Q2: 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(100)  # First one's free
'Insufficient funds'
>>> ch.withdraw(3)    # And the second
17
>>> 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
>>> ch.withdraw(5)  # Not enough to cover fee + withdraw
'Insufficient funds'
"""
withdraw_fee = 1
free_withdrawals = 2

``````

Use Ok to test your code:

``python3 ok -q FreeChecking``

## 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.

### Q3: 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 label(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``

### Q4: Balanced

Implement the `balanced` function, which returns whether `m` is a balanced mobile. A mobile is balanced if two conditions are met:

1. The torque applied by its left side is equal to that applied by its right side. Torque of the left side is the length of the left rod multiplied by the total weight hanging from that rod (a similar calculation is used for the right side).
2. Each of the submobiles hanging off its sides is balanced.

Hint: You may find it helpful to assume that weights themselves are 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``

### Q5: 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 label of each mobile.

>>> t, _, v = examples()
>>> label(with_totals(t))
3
>>> print(label(t))                           # t should not change
None
>>> label(with_totals(v))
9
>>> [label(end(s)) for s in sides(with_totals(v))]
[3, 6]
>>> [label(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

### Q6: 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``

### Q7: Next Fibonacci

Write a function `make_fib` that returns a function that returns the next Fibonacci number each time it is called. (The Fibonacci sequence begins with 0 and then 1, after which each element is the sum of the preceding two.) 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``

### Q8: 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 from 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``