{"assignment":{"_schema_version":2,"course_id":37,"date_created":"2022-06-28T19:00:00+00:00","date_modified":"2024-10-16T17:03:44.414095+00:00","extra_instructor_files":"","extra_starting_files":"","forked_id":null,"forked_version":null,"hidden":false,"id":1110,"instructions":"## Lists and Dataclasses\n\n* Lists: A sequence of data, accessible in order, all of the same type\n* Dataclasses: A bundle of data, accessible by field name, composed of many possible types\n\nRecall our two composite data types that we have learned so far: lists and dataclasses.\nLists allow us to represent many of the same kind of thing.\nDataclasses allow us to represent one thing that has many different parts.\nWe can access elements of a list in order sequentially or by position.\nWe can access fields of a dataclass by the name of the field.\nThey are both data structures that are composed of simpler primitive types and, as we will soon learn, also compatible with each other.\n\n## Nesting Data\n\n![On the left, a picture of a table of data representing four pets. On the right, a code representation of that data made by defining a Pet dataclass and then calling the pet constructor four times inside of a list literal.](bakery_nesting_list_dataclasses_tabular_code.png)\n\nTo represent more complicated reality, we must start nesting data structures inside of other data structures.\nA list of dataclasses is one of the most important ways to nest data structures because it can represent many things.\nA comparable idea is a table of data, where each column is of the same type and each row represents a single instance.\nAn individual row corresponds to a dataclass, whereas all the rows together represent the overall list.\nIn the image shown here, we have a table on the left representing four pets and code on the right also representing the same pets.\n\n## Nested Creation\n\n```python nested_creation\nfrom dataclasses import dataclass\n\n@dataclass\nclass Circle:\n    color: str\n    radius: int\n\ncircles = [Circle(\"red\", 4),\n           Circle(\"blue\", 10),\n           Circle(\"green\", 6)]\nprint(circles)\n```\n\nTo create lists, we use square brackets.\nTo create dataclasses, we call the constructor function.\nWe create a list of dataclasses in the same way: with the constructor calls inside of the square brackets, separated by commas.\nThe amount of whitespace inside of the square brackets and between the commas does not matter.\nSometimes, longer lists are placed on multiple lines to make it easier to read them, although that is not required.\n\n## What's in the List?\n\n```python nested_access\nfrom dataclasses import dataclass\n\n@dataclass\nclass Circle:\n    color: str\n    radius: int\n\ncircles = [Circle(\"red\", 4), Circle(\"blue\", 10)]\n\nprint(circles[0])\nprint(circles[1].radius)\n```\n\nAccessing nested data can be quite confusing, but the same consistent rules apply that we have learned throughout this course.\nIf you have a list of things, you can access specific elements using indexing.\nWhen you index a list of dataclasses, that means you get one dataclass out, and you can then access attributes of that element.\nIn the this code, the first `print` indexes the first element of the list and looks at the entire instance.\nThe second `print` indexes for the second instance and then accesses its `radius` field.\n\n## The Type of the Iteration Variable\n\n```python iteration_variable\nfrom dataclasses import dataclass\n\n@dataclass\nclass Animal:\n    name: str\n    weight: int\n    friendly: bool\n\nzoo = [Animal(\"Klaus\", 17, True),\n       Animal(\"Tigger\", 12, True),\n       Animal(\"Wrex\", 2, False)]\n\nfor an_animal in zoo:\n    print(an_animal)\n```\n\nSince we have a list of dataclasses, we can also iterate through the list.\nWhile inside the body of the iteration, we gain access to each instance and therefore the specific fields of each dataclass instance.\nA question to ask yourself is: What type of value is stored in the iteration variable? Many folks will incorrectly answer \"a string\" or \"an integer,\" because they are comfortable with those primitive types.\nHowever, the answer is the same as it was back when we were dealing with lists.\nThe iteration variable's type is the element type of the list.\nIf you have a list of dataclasses, then the iteration variable's type will be the type of the dataclass.\nIn this example, the `zoo` variable holds a list of `Animal` dataclasses.\nWhen we iterate through the `zoo` with a `for` loop, we create a new variable named `an_animal`.\nThat iteration variable `an_animal` will be of type `Animal`.\nNot a string representing the animal's name or its weight, but the entire `Animal` is stored in that variable.\n\n## Using Fields\n\n```python using_fields\nfrom dataclasses import dataclass\n\n@dataclass\nclass Book:\n    name: str\n    price: float\n\nshelf = [Book(\"Count of Monte Cristo\", 5.00),\n         Book(\"Frankenstein\", 10.00),\n         Book(\"To Kill a Mockingbird\", 6.00)]\n\ntotal_price = 0\n# Sum Pattern\nfor a_book in shelf:\n    total_price = total_price + a_book.price\nprint(total_price)\n```\n\nThe code here demonstrates how we can still apply the loop patterns we have seen so far.\nThe only major difference to the sum pattern is that we cannot use the `a_book` variable directly.\nThe `total_price` variable is an integer, while the `a_book` variable is a `Book` instance.\nThe addition operator is not compatible with those datatypes.\nInstead, we must specifically access the `price` field of `a_book` and add that instead.\nBy correctly accessing the right field, we can unpack the complex data structure and use its juicy goodness.\n\n## Example: Filter and Map\n\n```python example-lod-function\nfrom bakery import assert_equal\nfrom dataclasses import dataclass\n\n@dataclass\nclass Circle:\n    color: str\n    radius: int\n\n# Filter and Map\ndef get_red_circles(circles: list[Circle]) -> list[Circle]:\n    filtered = []\n    for circle in circles:\n        if circle.color == 'red':\n            filtered.append(circle)\n    return filtered\n\nmy_circles = [Circle(\"red\", 4), Circle(\"blue\", 10)]\nassert_equal(get_red_circles(my_circles), [Circle(\"red\", 4)])\n```\n\nPutting the `for` loop and attribute lookups inside of a function does not fundamentally change how everything works, either.\nIn this example, we have a `get_red_circles` function, which uses the filter pattern to only get the red circles from a list of circles.\nNote the parameter type and return type, which is a list of `Circle`s.\nAlthough there is a lot going on in this code, you have seen each concept before in some form.\nThe only thing complicating matter is that we are putting them all together in one place.\n\n## Example: Find Last\n\n```python find-last-example\nfrom bakery import assert_equal\nfrom dataclasses import dataclass\n\n@dataclass\nclass Fruit:\n    name: str\n    tastes: str\n    color: str\n\ndef get_last_sweet_fruit(fruits: list[Fruit]) -> Fruit:\n    sweet_fruit = None\n    for fruit in fruits:\n        if fruit.tastes == \"sweet\":\n            sweet_fruit = fruit\n    return sweet_fruit\n\nassert_equal(get_last_sweet_fruit([Fruit(\"lemon\", \"sour\", \"yellow\"),\n                                   Fruit(\"apple\", \"sweet\", \"red\"),\n                                   Fruit(\"grapes\", \"sweet\", \"purple\"),\n                                   Fruit(\"lime\", \"sour\", \"green\")]),\n             Fruit(\"grapes\", \"sweet\", \"purple\"))\nassert_equal(get_last_sweet_fruit([Fruit(\"lemon\", \"sour\", \"yellow\")]), None)\n```\n\nNow let's study an example function that consumes a list of dataclasses and finds the last element that matches a condition.\nSpecifically, we will take in a list of fruits (which have a `name`, `taste`, and `color`) and find the last sweet fruit.\nIf there is no sweet fruit in the list, then the function can return `None` instead.\n\nIf we had only wanted the last fruit in the list, it would have been enough to just `return fruits[-1]`.\nBut, because we need the last _sweet_ fruit, we use the find pattern to iterate through the list and check each one.\nWe skip over any fruit that is not sweet, updating the `found` variable whenever we encounter a sweet fruit.\n\nIf there are no sweet fruits in the list, we return `None` instead.\nWe wrote a test case to draw attention to this situation (and we would probably write a lot more test cases, in practice.) The default value can vary for the find pattern, depending on the needs of the overall program.\nYou might return a string value, a special default instance of the dataclass, or some other meaningful value.\n\nAn important detail is to logically separate the `sweet_fruit` variable (holding the result) and the `fruit` iteration variable (holding each possible `Fruit` in turn.) Sometimes, beginners will confuse the two variables and use `sweet_fruit` as the iteration variable.\nRemember, the iteration variable takes on each value of the original list (which is composed of fruits, some of which may not be sweet).\nIf you named the iteration variable `sweet_fruit`, then during the first iteration of the loop (the lemon), the variable's name would be inaccurate (since lemons are not sweet fruits.)\n\n\n## Example: Take and Count\n\n```python name-of-biggest\nfrom bakery import assert_equal\nfrom dataclasses import dataclass\n\n@dataclass\nclass Weather:\n    temperature: int\n    rainfall: int\n    is_sunny: bool\n\ndef days_until_raining(reports: list[Weather]) -> int:\n    non_rainy_days = 0\n    found_rainy_day = False\n    for report in reports:\n        if report.rainfall > 0:\n            found_rainy_day = True\n        elif not found_rainy_day:\n            non_rainy_days += 1\n    return non_rainy_days\n\nassert_equal(days_until_raining([]), 0)\nassert_equal(days_until_raining([Weather(54, 0, True), Weather(53, 0, True),\n                                 Weather(50, 0, False), Weather(52, 1, False),\n                                 Weather(55, 0, True), Weather(56, 2, True)]), 3)\n```\n\nThe next example will combine two other patterns: take and count.\nRecall that the take pattern consumes a list and produces a new list with just the elements that match the condition.\nThe count pattern reports how many elements are in the list.\nIn this modification of the take pattern, instead of producing a new list like the map pattern, we can follow the behavior of the count pattern to increment the accumulation variable instead.\n\nAs a context, consider a list of weather reports over time.\nEach report includes the temperature, inches of rainfall, and whether the day was sunny.\nWhat if you wanted to know how many days until the first day that it started raining?\n\nThe pattern begins by initializing the `non_rainy_days` to `0` and the `found_rainy_day` flag to be `False`.\nWe go through each `Weather` report, checking to see if the current report is a rainy day (where the `rainfall` is greater than `0`).\nWhen we find one, we negate the `found_rainy_day` flag.\nIf the day is not rainy, then we follow the `elif` branch and confirm that we have not yet found a rainy day before now.\nIf we have not, then we increment the number of `non_rainy_days` seen.\nAfter the loop is over, we return the `non_rainy_days` as the answer.\n\nThe second test case demonstrates the difference between the take pattern and the filter pattern.\nIf we filtered and then counted the number of non-rainy days, the result would be `4`.\nHowever, the take pattern allows us to only consider the non-rainy days _before_ the first rainy day (which is the fourth element of the list.)\n\n## Example: Maximum\n\n```python max\nfrom bakery import assert_equal\nfrom dataclasses import dataclass\n\n@dataclass\nclass Animal:\n    name: str\n    weight: int\n    friendly: bool\n\ndef name_of_biggest(animals: list[Animal]) -> str:\n    if not animals: # Remember the empty list case!\n        return \"No animals found.\"\n    biggest = animals[0]\n    for animal in animals:\n        if animal.weight > biggest.weight:\n            biggest = animal\n    return biggest.name\n\nassert_equal(name_of_biggest([Animal(\"Klaus\", 17, True),\n                              Animal(\"Tigger\", 12, True),\n                              Animal(\"Wrex\", 2, False)]), \"Klaus\")\nassert_equal(name_of_biggest([]), \"No animals found.\")\n```\n\nFrequently, we want to use one field when applying the pattern but use a different field in the return value.\nFor example, you could filter a list based on an attribute but map the list based on a different attribute.\nOr, in the case of the following example, we will find the maximum of a list using one field but return the value associated with a different field.\n\nConsider a list of animals representing the creatures of a zoo.\nWe might ask to find the name of the heaviest animal.\nThis would require the maximum pattern, but with careful modifications to the conditional check.\nRemember, you cannot directly compare the order of two dataclasses.\nInstead, you need to compare the fields of the dataclasses.\n\nThe function begins by checking that the list of animals is not empty.\nIf the list was empty, then the first line of the maximum pattern would cause an IndexError (since there is no first animal to grab.) But, since we return early if there are no animals at all, we can safely start by taking the first animal from the list and treating that animal as the `biggest` (so far.) Then, we iterate through each `animal` in the `animals` given, checking if that animal's weight is greater than the `biggest` animal so far.\nWhenever we find an animal with more weight, we update the `biggest` variable to reference that animal instead.\n\n## Stack/Heap Diagram of Maximum\n\n![A stack/heap diagram where the stack has three variables pointing to data on the heap. The first variable points to a list, the second variable points to an instance in that list, and the third variable points to a different instance in that list.](bakery_nesting_list_dataclasses_stack_heap.png)\n\nThe stack/heap diagram shown here visualizes how the memory is laid out during the second iteration of the loop.\nThe `animals` list has three elements, each of which is a dataclass instance of `Animal`.\nDuring the second iteration of the loop, the `animal` variable is pointing at the second instance, while the `biggest` variable is still pointing at the first element.\nAt the end of the iteration, the `biggest` will still point to the first `Animal`.\nThis allows us to `return biggest.name` to get the name of the biggest animal.\n\n## Example: Max and Filter Composition\n\n```python max-filter-composition\nfrom bakery import assert_equal\nfrom dataclasses import dataclass\n\n@dataclass\nclass Animal:\n    name: str\n    weight: int\n    friendly: bool\n\ndef remove_friendly(animals: list[Animal]) -> list[Animal]:\n    non_friendly_animals = []\n    for animal in animals:\n        if not animal.friendly:\n            non_friendly_animals.append(animal)\n    return non_friendly_animals\n\ndef meanest(animals: list[Animal]) -> str:\n    animals = remove_friendly(animals) # First remove friendly\n    if not animals: # Check empty list case!\n        return \"No unfriendly animals found.\"\n    biggest = animals[0]\n    for animal in animals:\n        if animal.weight > biggest.weight:\n            biggest = animal\n    return biggest.name\n\nassert_equal(meanest([Animal(\"K\", 17, True), Animal(\"T\", 12, True), Animal(\"W\", 2, False)]), \"W\")\nassert_equal(meanest([Animal(\"K\", 17, True), Animal(\"T\", 12, True)]), \"No unfriendly animals found.\")\n```\n\nThis final example will build directly on the previous example by adding one additional step: composing the maximum pattern with the filter pattern.\nThis time, instead of just looking for the heaviest animal, we will only look for the heaviest animal that is not friendly.\nThis is a little trickier, since not only could the `animals` list be empty, but the `animals` list might only have friendly animals.\nTo keep things simpler, we will do the new filtering work in a helper function (`remove_friendly`) that consumes a list of animals and returns a list of animals without any that are friendly.\n\nIt is very important that we remove friendly animals _before_ we guard against the empty list case.\nIf we swapped the order of those lines, then we would not correctly handle the case where the list has only friendly animals.\nComposition is a powerful tool for keeping complex logic in check.\nIf we tried to do the filtering in the same loop as the maximum pattern, we are likely to make a mistake when handling cases like when the first animal of the list is the biggest but also friendly.\nAnother advantage of having the helper function is that the function can be tested separately to ensure that we are correctly removing the friendly animals.\n\n## Summary\n\n- Lists can have instances of dataclasses, just like any other primitive type.\n- When you index a list of dataclasses, you get a specific instance and can access the instance's attributes.\n- When iterating over a list of dataclasses, the iteration variable will take on the element type of the list, which will be the type of the dataclass.\n- Lists of dataclasses are like a table of data that allows you to abstract more complex reality.\n- You can create a list of dataclasses using list literal syntax (square brackets) and the dataclass constructor functions.\n- You can apply the same loop patterns for primitive data over lists of dataclasses.\n  - Frequently, the loop pattern will use multiple fields of the dataclass instances.\n  - When applying the maximum pattern, you cannot directly compare the instances and instead should compare their fields.\n- A stack/heap diagram can visualize the relationship between the iteration variable, the iteration list, and the accumulation variable \u2014 all of which point to instances of data on the heap.\n\n","ip_ranges":"","name":"8A1) Lists of Dataclasses Reading","on_change":"","on_eval":"","on_run":"","owner_id":1,"owner_id__email":"acbart@udel.edu","points":0,"public":true,"reviewed":false,"sample_submissions":[],"settings":"{\n  \"header\": \"Lists of Dataclasses\",\n  \"youtube\": {\n    \"Bart\": \"TdKsQeO-gd8\",\n    \"Amy\": \"KrBevOVW6NE\"\n  },\n  \"video\": {\n    \"Bart\": \"https://blockpy.cis.udel.edu/videos/bakery_nesting_list_dataclasses-Bart.mp4\",\n    \"Amy\": \"https://blockpy.cis.udel.edu/videos/bakery_nesting_list_dataclasses-Amy.mp4\"\n  },\n  \"summary\": \"\",\n  \"small_layout\": true\n}","starting_code":"","subordinate":true,"tags":[],"type":"reading","url":"bakery_nesting_list_dataclasses_read","version":13},"ip":"216.73.216.157","submission":{"_schema_version":3,"assignment_id":1110,"assignment_version":13,"attempts":0,"code":"","correct":false,"course_id":37,"date_created":"2026-05-20T12:41:56.930659+00:00","date_due":"","date_graded":"","date_locked":"","date_modified":"2026-05-20T12:41:56.930659+00:00","date_started":"","date_submitted":"","endpoint":"","extra_files":"","feedback":"","grading_status":"NotReady","id":2036751,"score":0.0,"submission_status":"Started","time_limit":"","url":"submission_url-28da725a-312c-4e8a-9209-af278f51fbc0","user_id":2044660,"user_id__email":"","version":0},"success":true}
