diff --git a/_internal/targets.json b/_internal/targets.json index 872065524..28bed0932 100644 --- a/_internal/targets.json +++ b/_internal/targets.json @@ -1 +1 @@ -[{"due": "10/23/24", "link": "hw/hw07/", "name": "HW 07: Object-Oriented Programming", "piazza_name": "HW 07", "release": "10/16/24"}, {"due": "10/25/24", "link": "lab/lab08/", "name": "Lab 08: Inheritance, Linked Lists", "piazza_name": "Lab 08", "release": "10/21/24"}, {"due": "11/20/24", "link": "proj/ants/", "name": "Ants", "piazza_name": "Ants", "release": "10/16/24"}] +[{"due": "10/23/24", "link": "hw/hw07/", "name": "HW 07: Object-Oriented Programming", "piazza_name": "HW 07", "release": "10/16/24"}, {"due": "10/25/24", "link": "lab/lab08/", "name": "Lab 08: Inheritance, Linked Lists", "piazza_name": "Lab 08", "release": "10/21/24"}, {"due": "11/4/24", "link": "hw/hw08/", "name": "HW 08: Linked Lists", "piazza_name": "HW 08", "release": "10/23/24"}, {"due": "11/20/24", "link": "proj/ants/", "name": "Ants", "piazza_name": "Ants", "release": "10/16/24"}] diff --git a/disc/disc08/index.html b/disc/disc08/index.html index a5235e408..59b9eed87 100644 --- a/disc/disc08/index.html +++ b/disc/disc08/index.html @@ -327,9 +327,6 @@

Q2: Overlap

Take a vote to decide whether to use recursion or iteration. Either way works (and the solutions are about the same complexity/difficulty).

- -
Run in 61A Code diff --git a/hw/hw08/construct_check.py b/hw/hw08/construct_check.py new file mode 100644 index 000000000..bee096e7e --- /dev/null +++ b/hw/hw08/construct_check.py @@ -0,0 +1,180 @@ +from ast import parse, NodeVisitor, Name + +# For error messages (student-facing) only +_NAMES = { + 'Add': '+', + 'And': 'and', + 'Assert': 'assert', + 'Assign': '=', + 'AnnAssign': '=', + 'AugAssign': 'op=', + 'BitAnd': '&', + 'BitOr': '|', + 'BitXor': '^', + 'Break': 'break', + 'Recursion': 'recursive call', + 'ClassDef': 'class', + 'Continue': 'continue', + 'Del': 'del', + 'Delete': 'delete', + 'Dict': '{...}', + 'DictComp': '{...}', + 'Div': '/', + 'Ellipsis': '...', + 'Eq': '==', + 'ExceptHandler': 'except', + 'ExtSlice': '[::]', + 'FloorDiv': '//', + 'For': 'for', + 'FunctionDef': 'def', + 'Filter': 'filter', + 'GeneratorExp': '(... for ...)', + 'Global': 'global', + 'Gt': '>', + 'GtE': '>=', + 'If': 'if', + 'IfExp': '...if...else...', + 'Import': 'import', + 'ImportFrom': 'from ... import ...', + 'In': 'in', + 'Index': '...[...]', + 'Invert': '~', + 'Is': 'is', + 'IsNot': 'is not ', + 'LShift': '<<', + 'Lambda': 'lambda', + 'List': '[...]', + 'ListComp': '[...for...]', + 'Lt': '<', + 'LtE': '<=', + 'Mod': '%', + 'Mult': '*', + 'NamedExpr': ':=', + 'Nonlocal': 'nonlocal', + 'Not': 'not', + 'NotEq': '!=', + 'NotIn': 'not in', + 'Or': 'or', + 'Pass': 'pass', + 'Pow': '**', + 'RShift': '>>', + 'Raise': 'raise', + 'Return': 'return', + 'Set': '{ ... } (set)', + 'SetComp': '{ ... for ... } (set)', + 'Slice': '[ : ]', + 'Starred': '', + 'Str': 'str', + 'Sub': '-', + 'Subscript': '[]', + 'Try': 'try', + 'Tuple': '(... , ... )', + 'UAdd': '+', + 'USub': '-', + 'While': 'while', + 'With': 'with', + 'Yield': 'yield', + 'YieldFrom': 'yield from', +} + +def check(source_file, checked_funcs, disallow, source=None): + """Checks that AST nodes whose type names are present in DISALLOW + (an object supporting 'in') are not present in the function(s) named + CHECKED_FUNCS in SOURCE. By default, SOURCE is the contents of the + file SOURCE_FILE. CHECKED_FUNCS is either a string (indicating a single + name) or an object of some other type that supports 'in'. CHECKED_FUNCS + may contain __main__ to indicate an entire module. Prints reports of + each prohibited node and returns True iff none are found. + See ast.__dir__() for AST type names. The special node name 'Recursion' + checks for overtly recursive calls (i.e., calls of the form NAME(...) where + NAME is an enclosing def.""" + return ExclusionChecker(disallow).check(source_file, checked_funcs, source) + +class ExclusionChecker(NodeVisitor): + """An AST visitor that checks that certain constructs are excluded from + parts of a program. ExclusionChecker(EXC) checks that AST node types + whose names are in the sequence or set EXC are not present. Its check + method visits nodes in a given function of a source file checking that the + indicated node types are not used.""" + + def __init__(self, disallow=()): + """DISALLOW is the initial default list of disallowed + node-type names.""" + self._disallow = set(disallow) + self._checking = False + self._errs = 0 + + def generic_visit(self, node): + if self._checking and type(node).__name__ in self._disallow: + self._report(node) + super().generic_visit(node) + + def visit_Module(self, node): + if "__main__" in self._checked_funcs: + self._checking = True + self._checked_name = self._source_file + super().generic_visit(node) + + def visit_Call(self, node): + if 'Recursion' in self._disallow and \ + type(node.func) is Name and \ + node.func.id in self._func_nest: + self._report(node, "should not be recursive") + self.generic_visit(node) + + def visit_FunctionDef(self, node): + self._func_nest.append(node.name) + if self._checking: + self.generic_visit(node) + elif node.name in self._checked_funcs: + self._checked_name = "Function " + node.name + checking0 = self._checking + self._checking = True + super().generic_visit(node) + self._checking = checking0 + self._func_nest.pop() + + def _report(self, node, msg=None): + node_name = _NAMES.get(type(node).__name__, type(node).__name__) + if msg is None: + msg = "should not contain '{}'".format(node_name) + print("{} {}".format(self._checked_name, msg)) + self._errs += 1 + + def errors(self): + """Returns the number of number of prohibited constructs found in + the last call to check.""" + return self._errs + + def check(self, source_file, checked_funcs, disallow=None, source=None): + """Checks that AST nodes whose type names are present in DISALLOW + (an object supporting the contains test) are not present in + the function(s) named CHECKED_FUNCS in SOURCE. By default, SOURCE + is the contents of the file SOURCE_FILE. DISALLOW defaults to the + argument given to the constructor (and resets that value if it is + present). CHECKED_FUNCS is either a string (indicating a single + name) or an object of some other type that supports 'in'. + CHECKED_FUNCS may contain __main__ to indicate an entire module. + Prints reports of each prohibited node and returns True iff none + are found. + See ast.__dir__() for AST type names. The special node name + 'Recursion' checks for overtly recursive calls (i.e., calls of the + form NAME(...) where NAME is an enclosing def.""" + + self._checking = False + self._source_file = source_file + self._func_nest = [] + if type(checked_funcs) is str: + self._checked_funcs = { checked_funcs } + else: + self._checked_funcs = set(checked_funcs) + if disallow is not None: + self._disallow = set(disallow) + if source is None: + with open(source_file, 'r', errors='ignore') as inp: + source = inp.read() + p = parse(source, source_file) + self._errs = 0 + + self.visit(p) + return self._errs == 0 \ No newline at end of file diff --git a/hw/hw08/hw08.ok b/hw/hw08/hw08.ok new file mode 100644 index 000000000..7e36af4ba --- /dev/null +++ b/hw/hw08/hw08.ok @@ -0,0 +1,24 @@ +{ + "name": "Homework 8", + "endpoint": "cal/c88c/fa24/hw08", + "src": [ + "hw08.py" + ], + "tests": { + "hw*.py": "doctest", + "tests/*.py": "ok_test" + }, + "default_tests": [ + "store_digits", + "deep_map_mut" + ], + "protocols": [ + "restore", + "file_contents", + "help", + "grading", + "analytics", + "unlock", + "backup" + ] +} \ No newline at end of file diff --git a/hw/hw08/hw08.py b/hw/hw08/hw08.py new file mode 100644 index 000000000..b570fb119 --- /dev/null +++ b/hw/hw08/hw08.py @@ -0,0 +1,85 @@ +def store_digits(n): + """Stores the digits of a positive number n in a linked list. + + >>> s = store_digits(1) + >>> s + Link(1) + >>> store_digits(2345) + Link(2, Link(3, Link(4, Link(5)))) + >>> store_digits(876) + Link(8, Link(7, Link(6))) + >>> store_digits(2450) + Link(2, Link(4, Link(5, Link(0)))) + >>> store_digits(20105) + Link(2, Link(0, Link(1, Link(0, Link(5))))) + >>> # a check for restricted functions + >>> import inspect, re + >>> cleaned = re.sub(r"#.*\\n", '', re.sub(r'"{3}[\s\S]*?"{3}', '', inspect.getsource(store_digits))) + >>> print("Do not use str or reversed!") if any([r in cleaned for r in ["str", "reversed"]]) else None + """ + "*** YOUR CODE HERE ***" + + +def deep_map_mut(func, s): + """Mutates a deep link s by replacing each item found with the + result of calling func on the item. Does NOT create new Links (so + no use of Link's constructor). + + Does not return the modified Link object. + + >>> link1 = Link(3, Link(Link(4), Link(5, Link(6)))) + >>> print(link1) + <3 <4> 5 6> + >>> # Disallow the use of making new Links before calling deep_map_mut + >>> Link.__init__, hold = lambda *args: print("Do not create any new Links."), Link.__init__ + >>> try: + ... deep_map_mut(lambda x: x * x, link1) + ... finally: + ... Link.__init__ = hold + >>> print(link1) + <9 <16> 25 36> + """ + "*** YOUR CODE HERE ***" + + +class Link: + """A linked list. + + >>> s = Link(1) + >>> s.first + 1 + >>> s.rest is Link.empty + True + >>> s = Link(2, Link(3, Link(4))) + >>> s.first = 5 + >>> s.rest.first = 6 + >>> s.rest.rest = Link.empty + >>> s # Displays the contents of repr(s) + Link(5, Link(6)) + >>> s.rest = Link(7, Link(Link(8, Link(9)))) + >>> s + Link(5, Link(7, Link(Link(8, Link(9))))) + >>> print(s) # Prints str(s) + <5 7 <8 9>> + """ + empty = () + + def __init__(self, first, rest=empty): + assert rest is Link.empty or isinstance(rest, Link) + self.first = first + self.rest = rest + + def __repr__(self): + if self.rest is not Link.empty: + rest_repr = ', ' + repr(self.rest) + else: + rest_repr = '' + return 'Link(' + repr(self.first) + rest_repr + ')' + + def __str__(self): + string = '<' + while self.rest is not Link.empty: + string += str(self.first) + ' ' + self = self.rest + return string + str(self.first) + '>' + diff --git a/hw/hw08/hw08.zip b/hw/hw08/hw08.zip new file mode 100644 index 000000000..0e56bc6ae Binary files /dev/null and b/hw/hw08/hw08.zip differ diff --git a/hw/hw08/index.html b/hw/hw08/index.html new file mode 100644 index 000000000..9795a95a9 --- /dev/null +++ b/hw/hw08/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Homework 8 | Data C88C Fall 2024 + + + + + + +
+ +
+
+
+

+ +Homework 8: Linked Lists + + + + + + +

+
+ + +

Due by 11:59pm on Monday, November 4

+ + + + + +

Instructions

+ +

Download hw08.zip. Inside the archive, you will find a file called +hw08.py, along with a copy of the ok autograder.

+ +

Submission: When you are done, submit the assignment by uploading all code files you've edited to Gradescope. 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 Gradescope. See Lab 0 for more instructions on +submitting assignments.

+ +

Using Ok: If you have any questions about using Ok, please +refer to this guide. + + + +

Grading: Homework is graded based on +correctness. Each incorrect problem will decrease the total score by one point. +This homework is out of 2 points. + + + + + +

Required Questions

+ + +
+ + +
+ +

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.

+ + +

YouTube link

+
+ + +

Linked Lists

+ + +

Q1: Store Digits

+ + +

Write a function store_digits that takes in an integer n and returns +a linked list containing the digits of n in the same order (from left to right).

+ +

Important: Do not use any string manipulation functions, such as str or reversed.

+ + + +
def store_digits(n):
+    """Stores the digits of a positive number n in a linked list.
+
+    >>> s = store_digits(1)
+    >>> s
+    Link(1)
+    >>> store_digits(2345)
+    Link(2, Link(3, Link(4, Link(5))))
+    >>> store_digits(876)
+    Link(8, Link(7, Link(6)))
+    >>> store_digits(2450)
+    Link(2, Link(4, Link(5, Link(0))))
+    >>> store_digits(20105)
+    Link(2, Link(0, Link(1, Link(0, Link(5)))))
+    >>> # a check for restricted functions
+    >>> import inspect, re
+    >>> cleaned = re.sub(r"#.*\\n", '', re.sub(r'"{3}[\s\S]*?"{3}', '', inspect.getsource(store_digits)))
+    >>> print("Do not use str or reversed!") if any([r in cleaned for r in ["str", "reversed"]]) else None
+    """
+    "*** YOUR CODE HERE ***"
+
+ +
+ +

Use Ok to test your code:

python3 ok -q store_digits
+ +
+ + +

Q2: Mutable Mapping

+ + +

Implement deep_map_mut(func, s), which applies the function func to each +element in the linked list s. If an element is itself a linked list, +recursively apply func to its elements as well.

+ +

Your implementation should mutate the original linked list. Do not +create any new linked lists. The function returns None.

+ +

Hint: You can use the built-in isinstance function to determine if an element is a linked list.

+ +
>>> s = Link(1, Link(2, Link(3, Link(4))))
+>>> isinstance(s, Link)
+True
+>>> isinstance(s, int)
+False
+ + + +

Construct Check: The final test case for this problem checks +that your function does not create any new linked lists. +If you are failing this doctest, make sure that you are not +creating link lists by calling the constructor, i.e.

+ +
s = Link(1)
+ + + +
def deep_map_mut(func, s):
+    """Mutates a deep link s by replacing each item found with the
+    result of calling func on the item. Does NOT create new Links (so
+    no use of Link's constructor).
+
+    Does not return the modified Link object.
+
+    >>> link1 = Link(3, Link(Link(4), Link(5, Link(6))))
+    >>> print(link1)
+    <3 <4> 5 6>
+    >>> # Disallow the use of making new Links before calling deep_map_mut
+    >>> Link.__init__, hold = lambda *args: print("Do not create any new Links."), Link.__init__
+    >>> try:
+    ...     deep_map_mut(lambda x: x * x, link1)
+    ... finally:
+    ...     Link.__init__ = hold
+    >>> print(link1)
+    <9 <16> 25 36>
+    """
+    "*** YOUR CODE HERE ***"
+
+ +
+ +

Use Ok to test your code:

python3 ok -q deep_map_mut
+ +
+ + +

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.

+ + + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw/hw08/ok b/hw/hw08/ok new file mode 100644 index 000000000..b84a800fe Binary files /dev/null and b/hw/hw08/ok differ diff --git a/index.html b/index.html index 8de984905..c6ab43840 100644 --- a/index.html +++ b/index.html @@ -1050,13 +1050,17 @@

Calendar


-Lab 07: Object-Oriented Programming +Lab 07: Object-Oriented Programming
Due Fri 10/18
+ +
@@ -1134,7 +1138,8 @@

Calendar

@@ -1175,7 +1180,7 @@

Calendar

@@ -1186,7 +1191,7 @@

Calendar

-HW 08: Linked Lists +HW 08: Linked Lists
Due Mon 11/4 @@ -1527,7 +1532,7 @@

Policies

+ + + + + + + + + + + + + + + + + + +Lab 7 Solutions | Data C88C Fall 2024 + + + + + + +
+ +
+
+
+

+ +Lab 7 Solutions + + + + + + +

+
+ +

Solution Files

+ + + +

Required Questions

+ + + + +
+ + +

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.

+ + +

YouTube link

+
+ + +

Object-Oriented Programming

+ + +

Here's a refresher on Object-Oriented Programming. It's okay to skip directly to +the questions and refer back here if you get stuck.

+ + + +
+ +

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 (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'.

+ +
+ + +

Q1: Bank Account

+ + +

Extend the BankAccount class to include a transactions attribute. This attribute should be a list that keeps track of each transaction made on the account. Whenever the deposit or withdraw method is called, a new Transaction instance should be created and added to the list, even if the action is not successful.

+ +

The Transaction class should have the following attributes:

+ +
    +
  • before: The account balance before the transaction.
  • +
  • after: The account balance after the transaction.
  • +
  • id: The transaction ID, which is the number of previous transactions (deposits or withdrawals) made on that account. The transaction IDs for a specific BankAccount instance must be unique, but this id does not need to be unique across all accounts. In other words, you only need to ensure that no two Transaction objects made by the same BankAccount have the same id.
  • +
+ +

In addition, the Transaction class should have the following methods:

+ +
    +
  • changed(): Returns True if the balance changed (i.e., before is different from after), otherwise returns False.
  • +
  • report(): Returns a string describing the transaction. The string should start with the transaction ID and describe the change in balance. Take a look at the doctests for the expected output.
  • +
+ + + +
class Transaction:
+    def __init__(self, id, before, after):
+        self.id = id
+        self.before = before
+        self.after = after
+
+    def changed(self):
+        """Return whether the transaction resulted in a changed balance."""
+
return self.before != self.after
+ def report(self): + """Return a string describing the transaction. + + >>> Transaction(3, 20, 10).report() + '3: decreased 20->10' + >>> Transaction(4, 20, 50).report() + '4: increased 20->50' + >>> Transaction(5, 50, 50).report() + '5: no change' + """ + msg = 'no change' + if self.changed(): +
if self.after < self.before: + verb = 'decreased' + else: + verb = 'increased' + msg = verb + ' ' + str(self.before) + '->' + str(self.after)
return str(self.id) + ': ' + msg + +class BankAccount: + """A bank account that tracks its transaction history. + + >>> a = BankAccount('Eric') + >>> a.deposit(100) # Transaction 0 for a + 100 + >>> b = BankAccount('Erica') + >>> a.withdraw(30) # Transaction 1 for a + 70 + >>> a.deposit(10) # Transaction 2 for a + 80 + >>> b.deposit(50) # Transaction 0 for b + 50 + >>> b.withdraw(10) # Transaction 1 for b + 40 + >>> a.withdraw(100) # Transaction 3 for a + 'Insufficient funds' + >>> len(a.transactions) + 4 + >>> len([t for t in a.transactions if t.changed()]) + 3 + >>> for t in a.transactions: + ... print(t.report()) + 0: increased 0->100 + 1: decreased 100->70 + 2: increased 70->80 + 3: no change + >>> b.withdraw(100) # Transaction 2 for b + 'Insufficient funds' + >>> b.withdraw(30) # Transaction 3 for b + 10 + >>> for t in b.transactions: + ... print(t.report()) + 0: increased 0->50 + 1: decreased 50->40 + 2: no change + 3: decreased 40->10 + """ + + # *** YOU NEED TO MAKE CHANGES IN SEVERAL PLACES IN THIS CLASS *** +
def next_id(self): + # There are many ways to implement this counter, such as using an instance + # attribute to track the next ID. + return len(self.transactions)
+ def __init__(self, account_holder): + self.balance = 0 + self.holder = account_holder +
self.transactions = []
+ def deposit(self, amount): + """Increase the account balance by amount, add the deposit + to the transaction history, and return the new balance. + """ +
self.transactions.append(Transaction(self.next_id(), self.balance, self.balance + amount))
self.balance = self.balance + amount + return self.balance + + def withdraw(self, amount): + """Decrease the account balance by amount, add the withdraw + to the transaction history, and return the new balance. + """ + if amount > self.balance: +
self.transactions.append(Transaction(self.next_id(), self.balance, self.balance))
return 'Insufficient funds' +
self.transactions.append(Transaction(self.next_id(), self.balance, self.balance - amount))
self.balance = self.balance - amount + return self.balance
+ +
+ +

Use Ok to test your code:

python3 ok -q BankAccount
+ +
+ + +

Q2: 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 be different 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
+    2024
+    >>> dime = mint.create(Dime)
+    >>> dime.year
+    2024
+    >>> Mint.present_year = 2104  # Time passes
+    >>> nickel = mint.create(Nickel)
+    >>> nickel.year     # The mint has not updated its stamp yet
+    2024
+    >>> nickel.worth()  # 5 cents + (80 - 50 years)
+    35
+    >>> mint.update()   # The mint's year is updated to 2104
+    >>> Mint.present_year = 2179     # 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 = 2024
+
+    def __init__(self):
+        self.update()
+
+    def create(self, coin):
+
return coin(self.year)
+ def update(self): +
self.year = Mint.present_year
+class Coin: + cents = None # will be provided by subclasses, but not by Coin itself + + def __init__(self, year): + self.year = year + + def worth(self): +
return self.cents + max(0, Mint.present_year - self.year - 50)
+class Nickel(Coin): + cents = 5 + +class Dime(Coin): + cents = 10
+ +
+ +

Use Ok to test your code:

python3 ok -q Mint
+ +
+ + +

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

+ + +

If you are in a regular section of CS 61A, fill out this lab attendance and feedback form. (If you are in the mega section, you don't need to fill out the form.)

+ +

Then, submit this assignment by uploading any files you've edited to the appropriate Gradescope assignment. Lab 00 has detailed instructions.

+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab/sol-lab07/lab07.ok b/lab/sol-lab07/lab07.ok new file mode 100644 index 000000000..d35251deb --- /dev/null +++ b/lab/sol-lab07/lab07.ok @@ -0,0 +1,25 @@ +{ + "name": "Lab 7", + "endpoint": "cal/c88c/fa24/lab07", + "src": [ + "lab07.py", + "classes.py" + ], + "tests": { + "lab*.py": "doctest", + "tests/*.py": "ok_test", + "classes.py": "doctest" + }, + "default_tests": [ + "BankAccount", + "Mint" + ], + "protocols": [ + "restore", + "file_contents", + "analytics", + "unlock", + "grading", + "backup" + ] +} \ No newline at end of file diff --git a/lab/sol-lab07/lab07.py b/lab/sol-lab07/lab07.py new file mode 100644 index 000000000..956bc3075 --- /dev/null +++ b/lab/sol-lab07/lab07.py @@ -0,0 +1,154 @@ +class Transaction: + def __init__(self, id, before, after): + self.id = id + self.before = before + self.after = after + + def changed(self): + """Return whether the transaction resulted in a changed balance.""" + return self.before != self.after + + def report(self): + """Return a string describing the transaction. + + >>> Transaction(3, 20, 10).report() + '3: decreased 20->10' + >>> Transaction(4, 20, 50).report() + '4: increased 20->50' + >>> Transaction(5, 50, 50).report() + '5: no change' + """ + msg = 'no change' + if self.changed(): + if self.after < self.before: + verb = 'decreased' + else: + verb = 'increased' + msg = verb + ' ' + str(self.before) + '->' + str(self.after) + return str(self.id) + ': ' + msg + +class BankAccount: + """A bank account that tracks its transaction history. + + >>> a = BankAccount('Eric') + >>> a.deposit(100) # Transaction 0 for a + 100 + >>> b = BankAccount('Erica') + >>> a.withdraw(30) # Transaction 1 for a + 70 + >>> a.deposit(10) # Transaction 2 for a + 80 + >>> b.deposit(50) # Transaction 0 for b + 50 + >>> b.withdraw(10) # Transaction 1 for b + 40 + >>> a.withdraw(100) # Transaction 3 for a + 'Insufficient funds' + >>> len(a.transactions) + 4 + >>> len([t for t in a.transactions if t.changed()]) + 3 + >>> for t in a.transactions: + ... print(t.report()) + 0: increased 0->100 + 1: decreased 100->70 + 2: increased 70->80 + 3: no change + >>> b.withdraw(100) # Transaction 2 for b + 'Insufficient funds' + >>> b.withdraw(30) # Transaction 3 for b + 10 + >>> for t in b.transactions: + ... print(t.report()) + 0: increased 0->50 + 1: decreased 50->40 + 2: no change + 3: decreased 40->10 + """ + + # *** YOU NEED TO MAKE CHANGES IN SEVERAL PLACES IN THIS CLASS *** + def next_id(self): + # There are many ways to implement this counter, such as using an instance + # attribute to track the next ID. + return len(self.transactions) + + def __init__(self, account_holder): + self.balance = 0 + self.holder = account_holder + self.transactions = [] + + def deposit(self, amount): + """Increase the account balance by amount, add the deposit + to the transaction history, and return the new balance. + """ + self.transactions.append(Transaction(self.next_id(), self.balance, self.balance + amount)) + self.balance = self.balance + amount + return self.balance + + def withdraw(self, amount): + """Decrease the account balance by amount, add the withdraw + to the transaction history, and return the new balance. + """ + if amount > self.balance: + self.transactions.append(Transaction(self.next_id(), self.balance, self.balance)) + return 'Insufficient funds' + self.transactions.append(Transaction(self.next_id(), self.balance, self.balance - amount)) + self.balance = self.balance - amount + return self.balance + + +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 + 2024 + >>> dime = mint.create(Dime) + >>> dime.year + 2024 + >>> Mint.present_year = 2104 # Time passes + >>> nickel = mint.create(Nickel) + >>> nickel.year # The mint has not updated its stamp yet + 2024 + >>> nickel.worth() # 5 cents + (80 - 50 years) + 35 + >>> mint.update() # The mint's year is updated to 2104 + >>> Mint.present_year = 2179 # 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 = 2024 + + def __init__(self): + self.update() + + def create(self, coin): + return coin(self.year) + + def update(self): + self.year = Mint.present_year + +class Coin: + cents = None # will be provided by subclasses, but not by Coin itself + + def __init__(self, year): + self.year = year + + def worth(self): + return self.cents + max(0, Mint.present_year - self.year - 50) + +class Nickel(Coin): + cents = 5 + +class Dime(Coin): + cents = 10 + diff --git a/lab/sol-lab07/lab07.zip b/lab/sol-lab07/lab07.zip new file mode 100644 index 000000000..a3e903164 Binary files /dev/null and b/lab/sol-lab07/lab07.zip differ diff --git a/lab/sol-lab07/ok b/lab/sol-lab07/ok new file mode 100644 index 000000000..b84a800fe Binary files /dev/null and b/lab/sol-lab07/ok differ diff --git a/lecture/lec15/index.html b/lecture/lec15/index.html index 024e3286b..4dedfdafb 100644 --- a/lecture/lec15/index.html +++ b/lecture/lec15/index.html @@ -152,6 +152,8 @@

Lecture Playlist