Homework 8

Due Tuesday 11-November, at 11:59pm


To start

  1. Create a folder named hw8
  2. Create a new file hw8.py in that folder
  3. Edit hw8.py and add the functions and some testcases as required
  4. When you have completed and fully tested hw8, submit hw8.py to Gradescope. For this hw, you may submit up to 15 times, but only your last submission counts.
  5. This homework is individual. You are expected to write your own code and submit work that reflects your own understanding. You may discuss concepts with other students in the course, and you may use permitted resources, but you must follow the collaboration policy:
    • Do not copy code from any source.
    • If you look at code that you did not write, you must follow the 5-Minute Rule.
    • You are responsible for protecting your own work so others cannot copy it. See the academic honesty policy for more details.
  6. After you submit to Gradescope, make sure you check your score. If you aren’t sure how to do this, then ask a CA or Professor.
  7. There is no partial credit on Gradescope testcases. Your Gradescope score is your Gradescope score.
  8. Read the last bullet point again. Seriously, we won’t go back later and increase your Gradescope score for any reason. Even if you worked really hard and it was only a minor error…
  9. Do not hardcode the test cases in your solutions.
  10. Remember the course’s academic integrity policy. Solving the homework yourself is your best preparation for exams and quizzes; cheating or short-cutting your learning process in order to improve your homework score will actually hurt your course grade long-term.

Limitations

Do not use try/except this week. The autograder (or a manual CA review later) will reject your submission entirely if you do.

A Note About Style Grading

Like in the previous assignment, we will be grading your code based on whether it follows the 15-112 style guide. We may deduct up to 10 points from your overall grade for style errors. We highly recommend that you try to write clean code with good style all along, rather than fixing your style issues at the end. Good style helps you code faster and with fewer bugs. It is totally worth it. In any case, style grading already started, so please use good style from now on!

A Note About Testing

There is no skeleton file provided this week, meaning that we also are not providing testcases. You should write testcases for the autograded function. (You can find some nice ways to test in the write-up below, but you will need to translate those to actual testcases.)

Problems

  1. Bird Class and Subclasses [15 pts]
    Write the Bird, Penguin, and MessengerBird classes so that they pass testBirdClasses and use the OOP constructs we learned this week as appropriate.

    def getLocalMethods(clss): import types # This is a helper function for the test function below. # It returns a sorted list of the names of the methods # defined in a class. It's okay if you don't fully understand it! result = [ ] for var in clss.__dict__: val = clss.__dict__[var] if (isinstance(val, types.FunctionType)): result.append(var) return sorted(result) def testBirdClasses(): print("Testing Bird classes...", end="") # A basic Bird has a species name, can fly, and can lay eggs bird1 = Bird("Parrot") assert(type(bird1) == Bird) assert(isinstance(bird1, Bird)) assert(bird1.fly() == "I can fly!") assert(bird1.countEggs() == 0) assert(str(bird1) == "Parrot has 0 eggs") bird1.layEgg() assert(bird1.countEggs() == 1) assert(str(bird1) == "Parrot has 1 egg") bird1.layEgg() assert(bird1.countEggs() == 2) assert(str(bird1) == "Parrot has 2 eggs") assert(getLocalMethods(Bird) == ['__init__', '__repr__', 'countEggs', 'fly', 'layEgg']) # A Penguin is a Bird that cannot fly, but can swim bird2 = Penguin("Emperor Penguin") assert(type(bird2) == Penguin) assert(isinstance(bird2, Penguin)) assert(isinstance(bird2, Bird)) assert(bird2.fly() == "No flying for me.") assert(bird2.swim() == "I can swim!") bird2.layEgg() assert(bird2.countEggs() == 1) assert(str(bird2) == "Emperor Penguin has 1 egg") assert(getLocalMethods(Penguin) == ['fly', 'swim']) # A MessengerBird is a Bird that can optionally carry a message bird3 = MessengerBird("War Pigeon", message="Top-Secret Message!") assert(type(bird3) == MessengerBird) assert(isinstance(bird3, MessengerBird)) assert(isinstance(bird3, Bird)) assert(not isinstance(bird3, Penguin)) assert(bird3.deliverMessage() == "Top-Secret Message!") assert(str(bird3) == "War Pigeon has 0 eggs") assert(bird3.fly() == "I can fly!") bird4 = MessengerBird("Homing Pigeon") assert(bird4.deliverMessage() == "") bird4.layEgg() assert(bird4.countEggs() == 1) assert(getLocalMethods(MessengerBird) == ['__init__', 'deliverMessage']) print("Done!")

  2. Person Class [15 pts]
    Write the class Person so that the following test code passes:
    def testPersonClass(): print('Testing Person Class...', end='') fred = Person('fred', 32) assert(isinstance(fred, Person)) assert(fred.getName() == 'fred') assert(fred.getAge() == 32) # Note: person.getFriends() returns a list of Person objects who # are the friends of this person, listed in the order that # they were added. # Note: person.getFriendNames() returns a list of strings, the # names of the friends of this person. This list is sorted! assert(fred.getFriends() == [ ]) assert(fred.getFriendsNames() == [ ]) wilma = Person('wilma', 35) assert(wilma.getName() == 'wilma') assert(wilma.getAge() == 35) assert(wilma.getFriends() == [ ]) wilma.addFriend(fred) assert(wilma.getFriends() == [fred]) assert(wilma.getFriendsNames() == ['fred']) assert(fred.getFriends() == [wilma]) # friends are mutual! assert(fred.getFriendsNames() == ['wilma']) wilma.addFriend(fred) assert(wilma.getFriends() == [fred]) # don't add twice! betty = Person('betty', 29) fred.addFriend(betty) assert(fred.getFriendsNames() == ['betty', 'wilma']) pebbles = Person('pebbles', 4) betty.addFriend(pebbles) assert(betty.getFriendsNames() == ['fred', 'pebbles']) barney = Person('barney', 28) barney.addFriend(pebbles) barney.addFriend(betty) barney.addFriends(fred) # add ALL of Fred's friends as Barney's friends assert(barney.getFriends() == [pebbles, betty, wilma]) assert(barney.getFriendsNames() == ['betty', 'pebbles', 'wilma']) fred.addFriend(wilma) fred.addFriend(barney) assert(fred.getFriends() == [wilma, betty, barney]) assert(fred.getFriendsNames() == ['barney', 'betty', 'wilma']) # sorted! assert(barney.getFriends() == [pebbles, betty, wilma, fred]) assert(barney.getFriendsNames() == ['betty', 'fred', 'pebbles', 'wilma']) print('Passed!')
    Note that your solution must work in general, and not hardcode to these specific test cases.

  3. Marble, ConstantMarble, and DarkeningMarble classes [20 pts]
    Write the Marble, ConstantMarble, and DarkeningMarble classes so that the following test code passes (and without hardcoding any cases, so any similar code would also pass). Also, you must use OOP properly (we may check this manually, after the autograder gives you a preliminary score).

    def testMarbleClasses(): print("Testing Marble classes...", end="") # A Marble takes a string (not a list) of comma-separated color names m1 = Marble('Pink,Cyan') assert(m1.colorCount() == 2) # pink and cyan assert(Marble.getMarbleCount() == 1) # we have created 1 marble so far # When converted to a string, the Marble includes the color names, # each separated by a comma and a space, and all lower-case, and listed # in alphabetical order: assert(str(m1) == '<Marble with colors: cyan, pink>') m2 = Marble('Red,Orange,yellow,GREEN') assert(str(m2) == '<Marble with colors: green, orange, red, yellow>') assert(m2.colorCount() == 4) assert(Marble.getMarbleCount() == 2) # we have created 2 marbles so far # This also works in a list: assert(str([m1]) == '[<Marble with colors: cyan, pink>]') # Equality works as expected: m3 = Marble('red,blue') m4 = Marble('BLUE,RED') m5 = Marble('red,green,blue') assert((m3 == m4) and (m3 != m5) and (m3 != "Don't crash here!")) assert(Marble.getMarbleCount() == 5) # we have created 5 marbles so far # You can add colors, which only change the marble if they are not present: assert(m3.addColor('Red') == False) # False means the color was not added, # because it was already there # and no changes here: assert(m3.colorCount() == 2) assert(str(m3) == '<Marble with colors: blue, red>') assert((m3 == m4) and (m3 != m5)) # Once more, but with a new color: assert(m3.addColor('green') == True) # True means the color was added! # and so these all change: assert(m3.colorCount() == 3) assert(str(m3) == '<Marble with colors: blue, green, red>') assert((m3 != m4) and (m3 == m5)) # A ConstantMarble is a marble that never changes its color: m6 = ConstantMarble('red,blue') assert(isinstance(m6, Marble)) assert(str(m6) == '<Marble with colors: blue, red>') assert(m6.addColor('green') == False) # constant marbles never change! assert(str(m6) == '<Marble with colors: blue, red>') assert(Marble.getMarbleCount() == 6) # we have created 6 marbles so far assert(getLocalMethods(ConstantMarble) == ['addColor']) # A DarkeningMarble is a marble that prefixes 'dark' to any colors # that are added after it is first created. # Note: for full credit, you must use super() properly here! m7 = DarkeningMarble('red,blue') assert(isinstance(m7, Marble)) assert(str(m7) == '<Marble with colors: blue, red>') # not darkened assert(m7.addColor('green') == True) # but green will become darkgreen assert(str(m7) == '<Marble with colors: blue, darkgreen, red>') assert(Marble.getMarbleCount() == 7) # we have created 7 marbles so far assert(getLocalMethods(DarkeningMarble) == ['addColor']) print("Passed!")

  4. OOP: Cart and WishList classes [25 pts]
    Write the classes Cart and WishList so that the test code runs as specified. Do not hardcode against the values used in the testcases, though you can assume the testcases cover the needed functionality. For full credit, you must use proper object-oriented design, including good inheritance and avoiding unnecessary code duplication.
    # A (Shopping) Cart takes a string (not a list) of comma-separated items # A cart stores items, and items are case insensitive m1 = Cart('milk,eggs,MILK') assert(m1.itemCount() == 2) # milk and eggs # The .getItems method should return a set of unique items present in the cart assert(m1.getItems() == {'milk', 'eggs'}) # Note that these items are lowercase. Ignore case and duplicates m2 = Cart('Tomatoes,ONION,water,chips,CHIPS') assert(m2.getItems() == {'tomatoes','onion','water','chips'}) assert(m2.itemCount() == 4) # Still just four items assert(str(m2) == "Shopping Cart: 4 item(s)") m3 = Cart('MILK') assert(str(m3) == "Shopping Cart: 1 item(s)") # You can add one new item at a time: assert(m3.addItem('milk') == False) # Return False if the item is already present assert(m3.addItem('eggs') == True) # Return True if we added a new item assert(m3.itemCount() == 2) assert(m3.getItems() == {'milk', 'eggs'}) assert((m3.getItems() == m1.getItems()) and (m3.getItems != m2.getItems())) # Once more, but with a new item: assert(m3.addItem('water') == True) # True means the item was added! # and so these all change: assert(m3.itemCount() == 3) assert(m3.getItems() == {'milk', 'eggs','water'}) # Lastly, all carts start out open assert(m1.status == 'open') # ...unless you pay them m1.checkout() assert(m1.status == 'closed') m1.checkout() assert(m1.status == 'closed') # still closed # and you cannot add any more items assert(m1.addItem('tomatoes') == False) # Return False if the cart has been checked out assert(m1.status == 'closed') # still closed # A Wish List is a shopping cart that it always remains open w1 = WishList("LEMON") assert(str(w1) == "Wish List: 1 item(s)") assert(w1.itemCount() == 1) assert(w1.getItems() == {'lemon'}) assert(w1.addItem("Salt") == True) assert(w1.getItems() == {'lemon', 'salt'}) assert(w1.status == 'open') w1.checkout() # does nothing, the wish list stays open assert(w1.status == 'open')

  5. Vending Machine Class [25 pts]
    Write the VendingMachine class so that it passes testVendingMachineClass, and uses the OOP constructs we learned this week as appropriate.

    Hint: Make sure you're representing money properly wherever it might appear in the string representation of a Vending Machine. The test cases shown here don't cover every possible case...

    def testVendingMachineClass(): print("Testing Vending Machine class...", end="") # Vending machines have three main properties: # how many bottles they contain, the price of a bottle, and # how much money has been paid. A new vending machine starts with no # money paid. vm1 = VendingMachine(100, 125) assert(str(vm1) == "Vending Machine:<100 bottles; $1.25 each; $0 paid>") assert(vm1.isEmpty() == False) assert(vm1.getBottleCount() == 100) assert(vm1.stillOwe() == 125) # When the user inserts money, the machine returns a message about their # status and any change they need as a tuple. assert(vm1.insertMoney(20) == ("Still owe $1.05", 0)) assert(str(vm1) == "Vending Machine:<100 bottles; $1.25 each; $0.20 paid>") assert(vm1.stillOwe() == 105) assert(vm1.getBottleCount() == 100) assert(vm1.insertMoney(5) == ("Still owe $1", 0)) # When the user has paid enough money, they get a bottle and # the money owed resets. assert(vm1.insertMoney(100) == ("Got a bottle!", 0)) assert(vm1.getBottleCount() == 99) assert(vm1.stillOwe() == 125) assert(str(vm1) == "Vending Machine:<99 bottles; $1.25 each; $0 paid>") # If the user pays too much money, they get their change back with the # bottle. assert(vm1.insertMoney(500) == ("Got a bottle!", 375)) assert(vm1.getBottleCount() == 98) assert(vm1.stillOwe() == 125) # Machines can become empty vm2 = VendingMachine(1, 120) assert(str(vm2) == "Vending Machine:<1 bottle; $1.20 each; $0 paid>") assert(vm2.isEmpty() == False) assert(vm2.insertMoney(120) == ("Got a bottle!", 0)) assert(vm2.getBottleCount() == 0) assert(vm2.isEmpty() == True) # Once a machine is empty, it should not accept money until it is restocked. assert(str(vm2) == "Vending Machine:<0 bottles; $1.20 each; $0 paid>") assert(vm2.insertMoney(25) == ("Machine is empty", 25)) assert(vm2.insertMoney(120) == ("Machine is empty", 120)) assert(vm2.stillOwe() == 120) vm2.stockMachine(20) # Does not return anything assert(vm2.getBottleCount() == 20) assert(vm2.isEmpty() == False) assert(str(vm2) == "Vending Machine:<20 bottles; $1.20 each; $0 paid>") assert(vm2.insertMoney(25) == ("Still owe $0.95", 0)) assert(vm2.stillOwe() == 95) vm2.stockMachine(20) assert(vm2.getBottleCount() == 40) # We should be able to test machines for basic functionality vm3 = VendingMachine(50, 100) vm4 = VendingMachine(50, 100) vm5 = VendingMachine(20, 100) vm6 = VendingMachine(50, 200) vm7 = "Vending Machine" assert(vm3 == vm4) assert(vm3 != vm5) assert(vm3 != vm6) assert(vm3 != vm7) # should not crash! s = set() assert(vm3 not in s) s.add(vm4) assert(vm3 in s) s.remove(vm4) assert(vm3 not in s) assert(vm4.insertMoney(50) == ("Still owe $0.50", 0)) assert(vm3 != vm4) print("Done!")