Lab 6: Mutation
- Due: Wednesday 10/14 @ 11:59pm
- Points: 1
- Download: lab06.zip
Attendance
You need to submit the lab problems in addition to attending to get credit for lab. Students in mega lab only need to submit the lab problems. Preceptor interviews are highly recommended but not required for lab submissions.
If you are in regular lab, your TA will come around during lab to check you in. If you didn't attend for a good reason (such as being sick), fill out this form (within 2 weeks of your lab): attendance form.
Getting Started Videos
These videos may provide some helpful direction for tackling the coding problems on this assignment.
To see these videos, you should be logged into your berkeley.edu email.
Required Questions
Mutability
Consult the drop-down if you need a refresher on mutability. It's okay to skip directly to the questions and refer back here should you get stuck.
Mutability
Some objects in Python, such as lists and dictionaries, are mutable, meaning that their contents or state can be changed. Other objects, such as numeric types, tuples, and strings, are immutable, meaning they cannot be changed once they are created.
The two most common mutation operations for lists are item assignment and the
append method.
>>> s = [1, 3, 4]
>>> t = s # A second name for the same list
>>> t[0] = 2 # this changes the first element of the list to 2, affecting both s and t
>>> s
[2, 3, 4]
>>> s.append(5) # this adds 5 to the end of the list, affecting both s and t
>>> t
[2, 3, 4, 5]
There are many other list mutation methods:
append(elem): Addelemto the end of the list. ReturnNone.extend(s): Add all elements of iterablesto the end of the list. ReturnNone.insert(i, elem): Insertelemat indexi. Ifiis greater than or equal to the length of the list, thenelemis inserted at the end. This does not replace any existing elements, but only adds the new elementelem. ReturnNone.remove(elem): Remove the first occurrence ofelemin list. ReturnNone. Errors ifelemis not in the list.pop(i): Remove and return the element at indexi.pop(): Remove and return the last element.
Dictionaries also have item assignment (often used) and pop (rarely used).
>>> d = {2: 3, 4: 16}
>>> d[2] = 4
>>> d[3] = 9
>>> d
{2: 4, 4: 16, 3: 9}
>>> d.pop(4)
16
>>> d
{2: 4, 3: 9}
Q1: WWPD: List Mutation
Predict what Python will display when the following lines are entered into an
interactive session, then unlock the test to check your answers. Type FUNCTION
if you believe the answer is a function, ERROR if it errors, and NOTHING if
nothing is displayed:
python3 -m pytest -k list_mutation_wwpd --unlockUnlocking Examples
>>> s = [6, 7, 8]
>>> s.append(6)
>>> s
______
>>> s.insert(0, 9)
>>> s
______
>>> x = s.pop(1)
>>> s
______
>>> s.remove(x)
>>> s
______
>>> a, b = s, s[:]
>>> a is s
______
>>> b == s
______
>>> b is s
______
>>> a.pop()
______
>>> a + b
______
>>> s = [3]
>>> s.extend([4, 5])
>>> s
______
>>> a
______
>>> s.extend([s.append(9), s.append(10)])
>>> s
______
Q2: Insert Items
Write a function that takes in a list s, a value before, and a value
after. It modifies s in place by inserting after just after each value
equal to before in s. It returns s.
Important: No new lists should be created.
Note: If the values passed into
beforeandafterare equal, make sure you're not creating an infinitely long list while iterating through it. If you find that your code is taking more than a few seconds to run, the function may be in an infinite loop of inserting new values.
def insert_items(s: list[int], before: int, after: int) -> list[int]:
"""Insert after into s following each occurrence of before and then return s.
>>> test_s = [1, 5, 8, 5, 2, 3]
>>> new_s = insert_items(test_s, 5, 7)
>>> new_s
[1, 5, 7, 8, 5, 7, 2, 3]
>>> test_s
[1, 5, 7, 8, 5, 7, 2, 3]
>>> new_s is test_s
True
>>> double_s = [1, 2, 1, 2, 3, 3]
>>> double_s = insert_items(double_s, 3, 4)
>>> double_s
[1, 2, 1, 2, 3, 4, 3, 4]
>>> large_s = [1, 4, 8]
>>> large_s2 = insert_items(large_s, 4, 4)
>>> large_s2
[1, 4, 4, 8]
>>> large_s3 = insert_items(large_s2, 4, 6)
>>> large_s3
[1, 4, 6, 4, 6, 8]
>>> large_s3 is large_s
True
"""
"*** YOUR CODE HERE ***"
python3 -m pytest -k insert_itemsQ3: Group By
Write a function that takes in a list s and a function fn, and returns a
dictionary that groups the elements of s based on the result of applying
fn.
- The dictionary should have one key for each unique result of applying
fnto elements ofs. - The value for each key should be a list of all elements in
sthat, when passed tofn, produce that key (what it evaluates to).
In other words, for each element e in s, determine fn(e) and add e to
the list corresponding to fn(e) in the dictionary.
def group_by(s: list[int], fn) -> dict[int, list[int]]:
"""Return a dictionary of lists that together contain the elements of s.
The key for each list is the value that fn returns when called on any of the
values of that list.
>>> group_by([12, 23, 14, 45], lambda p: p // 10)
{1: [12, 14], 2: [23], 4: [45]}
>>> group_by(range(-3, 4), lambda x: x * x)
{9: [-3, 3], 4: [-2, 2], 1: [-1, 1], 0: [0]}
"""
grouped = {}
for ____ in ____:
key = ____
if key in grouped:
____
else:
grouped[key] = ____
return grouped
python3 -m pytest -k group_byObject Oriented Programming
Consult the drop-down if you need a refresher on object oriented programming (OOP) or inheritance. It's okay to skip directly to the questions and refer back here should you get stuck.
OOP
Object-oriented programming (OOP) uses objects and classes to organize programs. Here's an example of a class:
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 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. To evaluate the expression Car('red'), Python
creates a new Car object, then calls the __init__ function of the Car
class with self bound to the new object and color bound to 'red'.
Inheritance
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 also inherits 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 override the talk method.
We can use super() to refer to the superclass of self, and access any
superclass method as if we were an instance of the superclass. For example,
super().talk() in the Dog class calls the talk method from the Pet
class, but passes in the Dog instance as self.
Q4: Mint
A mint is a place where coins are made. In this question, you'll implement a
Mint class that can output a Coin with the correct year and worth.
- Each
Mintinstance has ayearstamp. Theupdatemethod sets theyearstamp of the instance to thepresent_yearclass attribute of theMintclass. - The
createmethod takes a subclass ofCoin(not an instance!), then creates and returns an instance of that subclass stamped with theMint's year (which may differ fromMint.present_yearif it has not been updated). - A
Coin'sworthmethod returns thecentsvalue of the coin plus one extra cent for each year of age beyond 50. A coin's age can be determined by subtracting the coin's year from thepresent_yearclass attribute of theMintclass.
class Mint:
"""A mint creates coins by stamping on years.
The update method sets the mint's stamp to Mint.present_year.
>>> mint = Mint()
>>> mint.year
2026
>>> dime = mint.create(Dime)
>>> dime.year
2026
>>> Mint.present_year = 2106 # Time passes
>>> nickel = mint.create(Nickel)
>>> nickel.year # The mint has not updated its stamp yet
2026
>>> nickel.worth() # 5 cents + (80 - 50 years)
35
>>> mint.update() # The mint's year is updated to 2106
>>> Mint.present_year = 2181 # More time passes
>>> mint.create(Dime).worth() # 10 cents + (75 - 50 years)
35
>>> Mint().create(Dime).worth() # A new mint has the current year
10
>>> dime.worth() # 10 cents + (155 - 50 years)
115
>>> Dime.cents = 20 # Upgrade all dimes!
>>> dime.worth() # 20 cents + (155 - 50 years)
125
"""
present_year = 2026
def __init__(self):
self.update()
def create(self, coin) -> 'Coin':
# Hint: coin is a class, such as Nickel or Dime, not an instance!
"*** YOUR CODE HERE ***"
def update(self) -> None:
"*** YOUR CODE HERE ***"
class Coin:
cents = None # will be provided by subclasses, but not by Coin itself
def __init__(self, year: int):
self.year = year
def worth(self) -> int:
assert self.cents is not None
"*** YOUR CODE HERE ***"
class Nickel(Coin):
cents = 5
class Dime(Coin):
cents = 10
python3 -m pytest -k MintQ5: Vending Machine
In this question you'll create a VendingMachine class that represents a vending
machine for some product. Fill in the VendingMachine class methods so that the
following interactions work as described.
A VendingMachine object returns strings describing its interactions. Study the
doctests carefully to understand the expected behavior of each method:
__init__(self, product, price): A vending machine sells a singleproductat a fixedprice. It starts with a stock of 0 and a balance of $0.restock(self, n): Addnto the stock and report the new stock level.add_funds(self, n): If out of stock, refuse the money and return it. Otherwise, addnto the current balance and report it.vend(self): Vend the product if there is enough stock and balance, giving change if the balance exceeds the price. Otherwise, explain what's needed.
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."""
"*** YOUR CODE HERE ***"
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
"""
"*** YOUR CODE HERE ***"
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
"""
"*** YOUR CODE HERE ***"
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.
"""
"*** YOUR CODE HERE ***"
python3 -m pytest -k VendingMachineSubmit Assignment
Submit this assignment by running Provenance: Prepare Submission Bundle from the VS Code command palette and uploading the resulting zip to Gradescope. The zip already contains the files you've edited. Lab 00 has detailed instructions.
Your responses to WWPD questions are not submitted, and they do not need to be. Lab credit is based on the code writing questions.