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): Add elem to the end of the list. Return None.
  • extend(s): Add all elements of iterable s to the end of the list. Return None.
  • insert(i, elem): Insert elem at index i. If i is greater than or equal to the length of the list, then elem is inserted at the end. This does not replace any existing elements, but only adds the new element elem. Return None.
  • remove(elem): Remove the first occurrence of elem in list. Return None. Errors if elem is not in the list.
  • pop(i): Remove and return the element at index i.
  • 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 --unlock
Unlocking 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 before and after are 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_items

Q3: 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 fn to elements of s.
  • The value for each key should be a list of all elements in s that, when passed to fn, 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_by

Object 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 Mint instance has a year stamp. The update method sets the year stamp of the instance to the present_year class attribute of the Mint class.
  • The create method takes a subclass of Coin (not an instance!), then creates and returns an instance of that subclass stamped with the Mint's year (which may differ from Mint.present_year if it has not been updated).
  • A Coin's worth method returns the cents value 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 the present_year class attribute of the Mint class.
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 Mint

Q5: 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 single product at a fixed price. It starts with a stock of 0 and a balance of $0.
  • restock(self, n): Add n to the stock and report the new stock level.
  • add_funds(self, n): If out of stock, refuse the money and return it. Otherwise, add n to 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 VendingMachine

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


Back to Top

Accessibility Nondiscrimination

Copyright ©2026, Regents of the University of California and respective authors.

This site is built following the Berkeley Class Site template, which is generously based on the Just the Class, and Just the Docs templates.

View all course offerings