Homework 4 Solutions
Solution Files
You can find the solutions in hw04.py.
Required Questions
Consult the drop-down if you need a refresher on the topics covered in this homework. It's
okay to skip directly to the questions and refer back
here should you get stuck.
class Car:
max_tires = 4
def __init__(self, color):
self.tires = Car.max_tires
self.color = color
def drive(self):
if self.tires < Car.max_tires:
return self.color + ' car cannot drive!'
return self.color + ' car goes vroom!'
def pop_tire(self):
if self.tires > 0:
self.tires -= 1
Class: The type of an object. The Car class (shown above) describes the characteristics of all Car
objects.
Object: A single instance of a class. In Python, a new object is created by calling a class.
>>> ferrari = Car('red')
Here, ferrari is a name bound to a Car object.
Class attribute: A variable that belongs to a class and is accessed via dot notation. The Car class has a max_tires attribute.
>>> Car.max_tires
4
Instance attribute: A variable that belongs to a particular object. Each Car object has a tires attribute and a color attribute. Like class attributes, instance attributes are accessed via dot notation.
>>> ferrari.color
'red'
>>> ferrari.tires
4
>>> ferrari.color = 'green'
>>> ferrari.color
'green'
Method: A function that belongs to an object and is called via dot notation. By convention, the first parameter of a method is self.
When one of an object's methods is called, the object is implicitly provided as the argument for self. For example, the drive method of the ferrari object is called with empty parentheses because self is implicitly bound to the ferrari object.
>>> ferrari = Car('red')
>>> ferrari.drive()
'red car goes vroom!'
We can also call the original Car.drive function. The original function does not belong to any particular Car object, so we must provide an explicit argument for self.
>>> ferrari = Car('red')
>>> Car.drive(ferrari)
'red car goes vroom!'
__init__: A special function that is called automatically when a new instance of a class is created.
Notice how the drive method takes in self as an argument, but it
looks like we didn't pass one in! This is because the dot notation
implicitly passes in ferrari as self for us. So in this example, self is bound to the
object called ferrari in the global frame.
To evaluate the expression Car('red'), Python creates a new Car object. Then, Python calls the __init__ function of the Car class with self bound to the new object and color bound to 'red'.
Trees
Recall the tree abstract data type: a tree is defined as having a label and some branches. Previously, we implemented the tree abstraction using Python lists. Let's look at another implementation using objects instead:
class Tree:
def __init__(self, label, branches=[]):
for b in branches:
assert isinstance(b, Tree)
self.label = label
self.branches = branches
def is_leaf(self):
return not self.branches
With this implementation, we can mutate a tree using attribute assignment, which wasn't possible in the previous implementation using lists. That's why we sometimes call these objects "mutable trees."
>>> t = Tree(3, [Tree(4), Tree(5)])
>>> t.label = 5
>>> t.label
5
To avoid redefining attributes and methods for similar classes, we can write a
single base class from which more specialized classes inherit. For
example, we can write a class called Pet and define Dog as a subclass of
Pet:
class Pet:
def __init__(self, name, owner):
self.is_alive = True # It's alive!!!
self.name = name
self.owner = owner
def eat(self, thing):
print(self.name + " ate a " + str(thing) + "!")
def talk(self):
print(self.name)
class Dog(Pet):
def talk(self):
super().talk()
print('This Dog says woof!')
Inheritance represents a hierarchical relationship between two or more
classes where one class is a more specific version of the other:
a dog is a pet.
(We use "is a" to describe this sort of relationship in OOP languages, not to refer to the Python is operator.)
Since Dog inherits from Pet, the Dog class will also inherit the
Pet class's methods, so we don't have to redefine __init__ or eat.
We do want each Dog to talk in a Dog-specific way,
so we can override the talk method.
We can use super() to refer to the superclass of self,
and access any superclass methods as if we were an instance of the superclass.
For example, super().talk() in the Dog class will call the talk
method from the Pet class, but passes in the Dog instance as the self.
Object-Oriented Programming
Q1: Vending Machine
In this question you'll create a vending machine that sells a single product and provides change when needed.
Implement the VendingMachine class, which models a vending machine for one specific product.
The methods of a VendingMachine object return strings to describe the machine’s status and operations.
Ensure that your output matches exactly with the strings provided in the doctests, including punctuation and spacing.
You may find Python's formatted string literals, or f-strings useful. A quick example:
>>> feeling = 'love' >>> course = 'CS 61A!' >>> combined_string = f'I {feeling} {course}' >>> combined_string 'I love CS 61A!'
class VendingMachine:
"""A vending machine that vends some product for some price.
>>> v = VendingMachine('candy', 10)
>>> v.vend()
'Nothing left to vend. Please restock.'
>>> v.add_funds(15)
'Nothing left to vend. Please restock. Here is your $15.'
>>> v.restock(2)
'Current candy stock: 2'
>>> v.vend()
'Please add $10 more funds.'
>>> v.add_funds(7)
'Current balance: $7'
>>> v.vend()
'Please add $3 more funds.'
>>> v.add_funds(5)
'Current balance: $12'
>>> v.vend()
'Here is your candy and $2 change.'
>>> v.add_funds(10)
'Current balance: $10'
>>> v.vend()
'Here is your candy.'
>>> v.add_funds(15)
'Nothing left to vend. Please restock. Here is your $15.'
>>> w = VendingMachine('soda', 2)
>>> w.restock(3)
'Current soda stock: 3'
>>> w.restock(3)
'Current soda stock: 6'
>>> w.add_funds(2)
'Current balance: $2'
>>> w.vend()
'Here is your soda.'
"""
def __init__(self, product: str, price: int):
"""Set the product and its price, as well as other instance attributes."""
self.product = product
self.price = price
self.stock = 0
self.balance = 0
def restock(self, n: int) -> str:
"""Add n to the stock and return a message about the updated stock level.
E.g., Current candy stock: 3
"""
self.stock += n
return f'Current {self.product} stock: {self.stock}'
def add_funds(self, n: int) -> str:
"""If the machine is out of stock, return a message informing the user to restock
(and return their n dollars).
E.g., Nothing left to vend. Please restock. Here is your $4.
Otherwise, add n to the balance and return a message about the updated balance.
E.g., Current balance: $4
"""
if self.stock == 0:
return f'Nothing left to vend. Please restock. Here is your ${n}.'
# Alternatively, we could have:
# return self.vend() + f' Here is your ${n}.'
self.balance += n
return f'Current balance: ${self.balance}'
def vend(self) -> str:
"""Dispense the product if there is sufficient stock and funds and
return a message. Update the stock and balance accordingly.
E.g., Here is your candy and $2 change.
If not, return a message suggesting how to correct the problem.
E.g., Nothing left to vend. Please restock.
Please add $3 more funds.
"""
if self.stock == 0:
return 'Nothing left to vend. Please restock.'
difference = self.price - self.balance
if difference > 0:
return f'Please add ${difference} more funds.'
message = f'Here is your {self.product}'
if difference != 0:
message += f' and ${-difference} change'
self.balance = 0
self.stock -= 1
return message + '.'
Use Ok to test your code:
python3 ok -q VendingMachine
Mutable Trees
Q2: Cumulative Mul
Write a function cumulative_mul that mutates the Tree t so that each node's
label is replaced by the product of its label and the labels of all its descendents.
Hint: Be careful of the order in which you mutate the current node's label and process its subtrees; which one should come first?
def cumulative_mul(t: Tree) -> None:
"""Mutates t so that each node's label becomes the product of its own
label and all labels in the corresponding subtree rooted at t.
>>> t = Tree(1, [Tree(3, [Tree(5)]), Tree(7)])
>>> cumulative_mul(t)
>>> t
Tree(105, [Tree(15, [Tree(5)]), Tree(7)])
>>> otherTree = Tree(2, [Tree(1, [Tree(3), Tree(4), Tree(5)]), Tree(6, [Tree(7)])])
>>> cumulative_mul(otherTree)
>>> otherTree
Tree(5040, [Tree(60, [Tree(3), Tree(4), Tree(5)]), Tree(42, [Tree(7)])])
"""
for b in t.branches:
cumulative_mul(b)
total = t.label
for b in t.branches:
total *= b.label
t.label = total
# Alternate solution using only one loop
def cumulative_mul(t: Tree) -> None:
for b in t.branches:
cumulative_mul(b)
t.label *= b.label
Use Ok to test your code:
python3 ok -q cumulative_mul
Q3: Prune Small
Removing some nodes from a tree is called pruning the tree.
Complete the function prune_small that takes in a Tree t and a number n.
For each node with more than n branches, keep only the n branches with the
smallest labels and remove (prune) the rest.
Hint: The
maxfunction takes in aniterableas well as an optionalkeyargument (which takes in a one-argument function). For example,max([-7, 2, -1], key=abs)would return-7sinceabs(-7)is greater thanabs(2)andabs(-1).The
removefunction takes in avalueargument and mutates a list by removing the first occurrence ofvalue. For example, ifl = [1, 2, 3, 2], callingl.remove(2)would mutatelto be[1, 3, 2]because it removes the first2it encounters.
def prune_small(t: Tree, n: int) -> None:
"""Prune the tree mutatively, keeping only the n branches
of each node with the smallest labels.
>>> t1 = Tree(6)
>>> prune_small(t1, 2)
>>> t1
Tree(6)
>>> t2 = Tree(6, [Tree(3), Tree(4)])
>>> prune_small(t2, 1)
>>> t2
Tree(6, [Tree(3)])
>>> t3 = Tree(6, [Tree(1), Tree(3, [Tree(1), Tree(2), Tree(3)]), Tree(5, [Tree(3), Tree(4)])])
>>> prune_small(t3, 2)
>>> t3
Tree(6, [Tree(1), Tree(3, [Tree(1), Tree(2)])])
"""
while len(t.branches) > n: largest = max(t.branches, key=lambda x: x.label) t.branches.remove(largest) for b in t.branches:
prune_small(b, n)
Use Ok to test your code:
python3 ok -q prune_small
Inheritance
Q4: Cat
Below is the implementation of a Pet class. Each pet has two instance attributes
(name and owner), as well as one instance method (talk).
class Pet:
def __init__(self, name, owner):
self.name = name
self.owner = owner
def talk(self):
print(self.name)
Implement the Cat class, which inherits from the Pet class seen above.
class Pet:
def __init__(self, name: str, owner: str) -> None:
self.name = name
self.owner = owner
def talk(self) -> None:
print(self.name)
class Cat(Pet):
"""
>>> my_cat = Cat("Furball", "Me", lives=2)
>>> my_cat.talk()
Meow!
>>> my_cat.name
'Furball'
>>> my_cat.lose_life()
>>> my_cat.is_alive
True
>>> my_cat.eat("poison")
Meow!
Furball ate a poison!
>>> my_cat.is_alive
False
>>> my_cat.lose_life()
'Cat is dead x_x'
"""
def __init__(self, name: str, owner: str, lives: int = 9) -> None:
assert type(lives) == int and lives > 0
Pet.__init__(self, name, owner)
self.lives = lives
self.is_alive = True
def talk(self) -> None:
"""A cat says 'Meow!' when asked to talk."""
print("Meow!")
def lose_life(self) -> str | None:
"""A cat can only lose a life if it has at least one
life. When there are zero lives left, the 'is_alive'
variable becomes False.
"""
if not self.is_alive:
return "Cat is dead x_x"
self.lives -= 1
if self.lives == 0:
self.is_alive = False
def eat(self, thing: str) -> None:
self.talk()
print(f"{self.name} ate a {thing}!")
if thing == "poison":
self.is_alive = False
Use Ok to test your code:
python3 ok -q Cat
Check Your Score Locally
You can locally check your score on each question of this assignment by running
python3 ok --score
This does NOT submit the assignment! When you are satisfied with your score, submit the assignment to Gradescope to receive credit for it.
Submit Assignment
Submit this assignment by uploading any files you've edited to the appropriate Gradescope assignment. Lab 00 has detailed instructions.
Exam Practice
Homework assignments will also contain prior exam-level questions for you to take a look at. These questions have no submission component; feel free to attempt them if you'd like a challenge!
- Spring 2022 MT2 Q8: CS61A Presents The Game of Hoop.
- Spring 2024 Final Q5: Trees
- Spring 2026 MT Q5: Making Pen Friends!