{"id":"class-examples-list","slug":"keeping-a-list","title":"A class that keeps a list","text":"Many classes keep a collection of things in a list attribute and offer methods to add to it, take from it and ask about it. The client doesn't touch the list itself; it uses the methods.\n\nThe string method `join` from part 4 comes in handy for showing such a list. As a reminder, it puts the strings of a list together into one string, with the string it is called on between them:\n\n```pycon\n>>> names = [\"Ann\", \"Bo\", \"Cy\"]\n>>> \", \".join(names)\n'Ann, Bo, Cy'\n>>> \" / \".join(names)\n'Ann / Bo / Cy'\n```\n\nRead `\", \".join(names)` as \"join the names with a comma and a space\". All the items have to be strings.\n\nHere is a waiting list for a busy restaurant. The first to arrive is the first to get a table:\n\n```python\nclass WaitingList:\n    def __init__(self):\n        self.names = []\n\n    def add(self, name):\n        self.names.append(name)\n\n    def next_up(self):\n        return self.names.pop(0)\n\n    def size(self):\n        return len(self.names)\n\n    def __str__(self):\n        if len(self.names) == 0:\n            return \"nobody waiting\"\n        return \", \".join(self.names)\n\n\ntables = WaitingList()\ntables.add(\"Ann\")\ntables.add(\"Bo\")\ntables.add(\"Cy\")\nprint(tables)\nprint(\"Table for\", tables.next_up())\nprint(tables, \"/\", tables.size(), \"waiting\")\n```\n\n```output\nAnn, Bo, Cy\nTable for Ann\nBo, Cy / 2 waiting\n```\n\n`next_up` both changes the list and returns the name it took out, thanks to `pop(0)`. The constructor makes a new empty list for every waiting list, so two restaurants never share one.\n\n### A list of tuples\n\nWhen each thing in the collection has several pieces of data, the list can hold tuples. This playlist keeps each song as a pair of its title and its length in seconds:\n\n```python\nclass Playlist:\n    def __init__(self, name):\n        self.name = name\n        self.songs = []\n\n    def add(self, title, seconds):\n        self.songs.append((title, seconds))\n\n    def total_seconds(self):\n        total = 0\n        for title, seconds in self.songs:\n            total += seconds\n        return total\n\n    def longest(self):\n        best = self.songs[0]\n        for song in self.songs:\n            if song[1] > best[1]:\n                best = song\n        return best[0]\n\n    def __str__(self):\n        titles = []\n        for title, seconds in self.songs:\n            titles.append(title)\n        return f\"{self.name}: \" + \", \".join(titles)\n\n\nroad_trip = Playlist(\"Road trip\")\nroad_trip.add(\"Highway Song\", 215)\nroad_trip.add(\"Desert Wind\", 342)\nroad_trip.add(\"Gas Station Blues\", 187)\nprint(road_trip)\nprint(road_trip.total_seconds(), \"seconds\")\nprint(\"Longest:\", road_trip.longest())\n```\n\n```output\nRoad trip: Highway Song, Desert Wind, Gas Station Blues\n744 seconds\nLongest: Desert Wind\n```\n\n`for title, seconds in self.songs:` takes each tuple apart into two variables, as you did with a dictionary's `items()`. In `longest`, `song[1]` is a song's length and `best[0]` the title of the longest one.\n","task":"Fill in the blanks, so that the bag keeps its items in a list and the program prints:\n\n```\nkeys, phone, apple\n```\n","kind":"fill","seed":"class Bag:\n    def __init__(self):\n        self.items = []\n\n    def put(self, item):\n        self.items.[[append]](item)\n\n    def __str__(self):\n        return \", \".[[join]](self.items)\n\n\nbag = Bag()\nbag.put(\"keys\")\nbag.put(\"phone\")\nbag.put(\"apple\")\nprint(bag)\n","solution":"class Bag:\n    def __init__(self):\n        self.items = []\n\n    def put(self, item):\n        self.items.append(item)\n\n    def __str__(self):\n        return \", \".join(self.items)\n\n\nbag = Bag()\nbag.put(\"keys\")\nbag.put(\"phone\")\nbag.put(\"apple\")\nprint(bag)\n","hints":["The first blank is the list method that adds an item to the end.","The second is the string method that puts a list of strings together, with `\", \"` between them: `join`."],"tests":[{"name":"Output","stdin":"","expect":"keys, phone, apple","hidden":false}],"require":[],"forbid":[],"more":[{"task":"Write a class `Cart` for a web shop's shopping cart. It keeps the products and their prices in a list. Give it these methods:\n\n- `add(product, price)` puts a product in the cart.\n- `remove(product)` takes the first product with that name out of the cart and returns `True`, or returns `False` when there is none.\n- `total()` returns the sum of the prices.\n- `__str__` returns the number of items and the total with two decimals, in the form shown below.\n\nThis code:\n\n```python\ncart = Cart()\ncart.add(\"socks\", 4.5)\ncart.add(\"hat\", 12)\ncart.add(\"socks\", 4.5)\nprint(cart)\nprint(cart.remove(\"hat\"), cart.remove(\"scarf\"))\nprint(cart)\n```\n\nprints\n\n```\nitems: 3, total 21.00\nTrue False\nitems: 2, total 9.00\n```\n","kind":"code","seed":"class Cart:\n    def __init__(self):\n        self.items = []\n\n\nif __name__ == \"__main__\":\n    cart = Cart()\n    cart.add(\"socks\", 4.5)\n    print(cart)\n","solution":"class Cart:\n    def __init__(self):\n        self.items = []\n\n    def add(self, product, price):\n        self.items.append((product, price))\n\n    def remove(self, product):\n        for item in self.items:\n            if item[0] == product:\n                self.items.remove(item)\n                return True\n        return False\n\n    def total(self):\n        total = 0\n        for product, price in self.items:\n            total += price\n        return total\n\n    def __str__(self):\n        return f\"items: {len(self.items)}, total {self.total():.2f}\"\n\n\nif __name__ == \"__main__\":\n    cart = Cart()\n    cart.add(\"socks\", 4.5)\n    print(cart)\n","hints":["Keep each product as a tuple: `self.items.append((product, price))`.","In `remove`, loop through the tuples. When `item[0] == product`, take that tuple out with the list method `remove` and return `True` right away. After the loop, return `False`.","`__str__` can use `len(self.items)` and `self.total()`; `:.2f` shows two decimals."],"tests":[{"name":"Test 1","stdin":"","call":"cart = Cart()\ncart.add(\"socks\", 4.5)\ncart.add(\"hat\", 12)\ncart.add(\"socks\", 4.5)\nprint(cart)\nprint(cart.remove(\"hat\"), cart.remove(\"scarf\"))\nprint(cart)\n","expect":"items: 3, total 21.00\nTrue False\nitems: 2, total 9.00","hidden":false},{"name":"Test 2","stdin":"","call":"cart = Cart()\nprint(cart, cart.total())\ncart.add(\"mug\", 8.25)\ncart.add(\"tea\", 3.1)\ncart.add(\"mug\", 7.0)\nprint(cart.remove(\"mug\"), cart.total())\nprint(cart.remove(\"tea\"), cart.remove(\"tea\"))\nprint(cart)\n","expect":"items: 0, total 0.00 0\nTrue 10.1\nTrue False\nitems: 1, total 7.00","hidden":true}],"require":[],"forbid":[]},{"task":"Write a class `TicketQueue` for the line at a ticket counter. Give it these methods:\n\n- `arrive(name)` puts the person at the end of the line.\n- `serve()` takes the first person out of the line and returns their name. When the line is empty, it returns the string `\"nobody\"`.\n- `position(name)` returns the person's place in the line, counting from 1, or 0 when they are not in it.\n- `__str__` returns the names in order with ` > ` between them, or `empty` when nobody is waiting.\n\nThis code:\n\n```python\nline = TicketQueue()\nline.arrive(\"Ann\")\nline.arrive(\"Bo\")\nline.arrive(\"Cy\")\nprint(line)\nprint(line.position(\"Cy\"), line.position(\"Dee\"))\nprint(line.serve())\nprint(line)\n```\n\nprints\n\n```\nAnn > Bo > Cy\n3 0\nAnn\nBo > Cy\n```\n","kind":"code","seed":"class TicketQueue:\n    pass\n\n\nif __name__ == \"__main__\":\n    line = TicketQueue()\n    line.arrive(\"Ann\")\n    line.arrive(\"Bo\")\n    print(line)\n","solution":"class TicketQueue:\n    def __init__(self):\n        self.names = []\n\n    def arrive(self, name):\n        self.names.append(name)\n\n    def serve(self):\n        if len(self.names) == 0:\n            return \"nobody\"\n        return self.names.pop(0)\n\n    def position(self, name):\n        for i in range(len(self.names)):\n            if self.names[i] == name:\n                return i + 1\n        return 0\n\n    def __str__(self):\n        if len(self.names) == 0:\n            return \"empty\"\n        return \" > \".join(self.names)\n\n\nif __name__ == \"__main__\":\n    line = TicketQueue()\n    line.arrive(\"Ann\")\n    line.arrive(\"Bo\")\n    print(line)\n","hints":["Keep the names in a list attribute that the constructor makes. `pop(0)` takes out the first one.","For `position`, go through the indexes with `for i in range(len(self.names)):`. When `self.names[i] == name`, return `i + 1`, since the places count from 1. After the loop, return 0.","`__str__` returns `\" > \".join(self.names)`, or `\"empty\"` when the list is empty."],"tests":[{"name":"Test 1","stdin":"","call":"line = TicketQueue()\nline.arrive(\"Ann\")\nline.arrive(\"Bo\")\nline.arrive(\"Cy\")\nprint(line)\nprint(line.position(\"Cy\"), line.position(\"Dee\"))\nprint(line.serve())\nprint(line)\n","expect":"Ann > Bo > Cy\n3 0\nAnn\nBo > Cy","hidden":false},{"name":"Test 2","stdin":"","call":"q = TicketQueue()\nprint(q)\nprint(q.serve(), q.position(\"Ann\"))\nq.arrive(\"Zoe\")\nq.arrive(\"Yan\")\nprint(q.position(\"Yan\"), q.serve(), q.position(\"Yan\"))\nprint(q.serve(), q.serve())\nprint(q)\n","expect":"empty\nnobody 0\n2 Zoe 1\nYan nobody\nempty","hidden":true}],"require":[],"forbid":[]}]}