Homework 6
Due by 11:59pm on Thursday, 10/5
Instructions
Download hw06.zip.
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):
"""Add amount to balance."""
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
"*** YOUR CODE HERE ***"
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
"*** YOUR CODE HERE ***"
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 amobile
orweight
. - 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
"*** YOUR CODE HERE ***"
def size(w):
"""Select the size of a weight."""
"*** YOUR CODE HERE ***"
def is_weight(w):
"""Whether w is a weight, not a mobile."""
"*** YOUR CODE HERE ***"
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:
- 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).
- 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
"""
"*** YOUR CODE HERE ***"
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]
"""
"*** YOUR CODE HERE ***"
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
"""
"*** YOUR CODE HERE ***"
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
"""
"*** YOUR CODE HERE ***"
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.
- A password-protected
withdraw
function, - The password with which that
withdraw
function was defined, and - 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):
"""Return a password-protected withdraw function that has joint access to
the balance of withdraw.
>>> w = make_withdraw(100, 'hax0r')
>>> w(25, 'hax0r')
75
>>> make_joint(w, 'my', 'secret')
'Incorrect password'
>>> j = make_joint(w, 'hax0r', 'secret')
>>> w(25, 'secret')
'Incorrect password'
>>> 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(25, 'password')
'Incorrect password'
>>> j2(5, 'secret')
"Your account is locked. Attempts: ['my', 'secret', 'password']"
>>> j(5, 'secret')
"Your account is locked. Attempts: ['my', 'secret', 'password']"
>>> w(5, 'hax0r')
"Your account is locked. Attempts: ['my', 'secret', 'password']"
>>> make_joint(w, 'hax0r', 'hello')
"Your account is locked. Attempts: ['my', 'secret', 'password']"
"""
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python3 ok -q make_joint