{"assignment":{"_schema_version":2,"course_id":37,"date_created":"2022-06-28T19:00:00+00:00","date_modified":"2024-09-30T17:18:47.291390+00:00","extra_instructor_files":"","extra_starting_files":"","forked_id":null,"forked_version":null,"hidden":false,"id":1081,"instructions":"## Directly Looping in Functions\n\n```python direct-average\ndef average(nums: list[int]) -> float:\n    total = 0\n    count = 0\n    for num in nums:\n        total = total + num\n        count = count + 1\n    return total / count\n\nprint(average([1, 4, 5]))\n```\n\nSometimes, we might need to find the mean (or average) of something/a value.\nFor example, finding the average temperatures for a particular month or the number of cupcakes sold each year at a bake sale.\nWe calculate the mean by adding together the elements and dividing that total by the number of elements.\n\nYou can calculate the mean, or average, of a list by adding together the elements and dividing by the number of elements.\nFinding the average is a common operation for a list.\nYou could write this function by creating a loop that combines the sum and count patterns.\nThis is a valid solution but ends up being a little complicated inside of the loop with the two statements.\n\n## Indirectly Looping in Functions\n\n```python\ndef sum(nums: list[int]) -> int:\n    sum = 0\n    for num in nums:\n        sum = sum + num\n    return sum\n\ndef count(nums: list[int]) -> int:\n    count = 0\n    for num in nums:\n        count = count + 1\n    return count\n\ndef average(nums: list[int]) -> float:\n    return sum(nums) / count(nums)\n```\n\n\nAlternatively, you could define functions to sum and count and then use those functions in your average function.\nThis version does not require a loop directly in the average function because the loop happens indirectly in a helper function.\nAlthough this version takes more code, each function only must do one thing, making it easier to test and debug each individual component.\n\n## Functional Decomposition\n\n![A diagram showing how a main function is decomposed into calls to several helper functions.](bakery_loops_decomposition_arrows.png)\n\nEarlier, we learned to break up complex functions into multiple parts using functional decomposition.\nWith loops, we finally see why that becomes necessary.\nThe control flow for a function with a loop is complicated.\nWhen you start nesting loops and if statements inside of functions, mistakes are likely to occur.\nFunctional decomposition allows us to tame that complexity by focusing on one conceptual step at a time.\n\n## Complex Combinations\n\n![A person is shown thinking about the many different actions they must take.](bakery_loops_decomposition_confusing.png)\n\nLet's say that we wanted to add up all the negative numbers in a list.\nYou could write a for loop that combines the filter pattern and the count pattern.\nYou might even succeed.\nBut what if you also needed to convert all the elements from strings to integers first? What if you needed to also find the average? Suddenly, combining all these patterns becomes difficult.\n\n## Composing Loop Patterns\n\n```python long-decomposition\ndef map_strs_to_ints(numbers: list[str]) -> list[int]:\n    integers = []\n    for number in numbers:\n        integers.append(int(number))\n    return integers\ndef remove_negatives(numbers: list[int]) -> list[int]:\n    positives = []\n    for number in numbers:\n        if number >= 0:\n            positives.append(number)\n    return positives\ndef average(nums: list[int]) -> int:\n    total = 0\n    count = 0\n    for num in nums:\n        total = total + num\n        count = count + 1\n    return total / count\n\ndef average_positives(numbers: list[str]) -> float:\n    return average(remove_negatives(map_strs_to_ints(numbers)))\n```\n\nInstead, let's focus on defining functions to solve each part of the puzzle in turn.\nWe define a `map_strs_to_ints` function that maps string conversion over a list of strings.\nWe define a `remove_negatives` function that filters out negative integers.\nWe use the count and sum patterns to define the `average` function that consume lists of integers to accumulate.\nThen, we can compose those functions as needed into an `average_positives` function.\nThis may seem like a lot more work \u2014 suddenly we must define and test five functions instead of one.\nBut the advantage is that each function is relatively easy to define and test.\nIf you make a mistake, you will be able to track it down very quickly.\n\n## Filter/Take and Min/Max\n\n```python min-max-compose\ndef only_questions(words: list[str]) -> list[str]:\n    questions = []\n    for word in words:\n        if \"?\" in word:\n            questions.append(word)\n    return questions\n\ndef last_word(words: list[str]) -> str:\n    last = words[0]\n    for word in words:\n        if word > last:\n            last = word\n    return last\n\n# Main function\ndef last_question_word(words: list[str]) -> str:\n    questions = only_questions(words)\n    # DEFENSIVE GUARD IS CRITICAL\n    if not questions:\n        return \"no question words!\"\n    return last_word(questions)\n```\n\nBoth the filter and take patterns consume a list and return a new list with potentially fewer elements.\nThis can cause trouble when composing these patterns with the min/max patterns.\nRecall that the min and max patterns expect a non-empty list.\nUsually, we must guard against empty lists before using these patterns.\nBut, if you guard too early, then you run the risk of still potentially having an empty list.\nIt is critical to delay the guard until just before you are going to index the list.\n\n## Summary\n\n- Loop patterns are useful on their own but can be even more useful when composed together.\n- If a function is using helper functions that loop, then the function may not need to loop directly (instead the function is indirectly looping).\n- When taking the minimum or maximum of a filtered/taken list, you must place a conditional guard between the patterns to make sure the list is not empty.\n- To determine the sequence of decomposed loop helper functions, you must think critically. However, you can also rely on the types of the functions to determine if they must be placed in a specific order.\n","ip_ranges":"","name":"6B2) Loop Composition 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\": \"Loop Composition\",\n  \"slides\": \"bakery_for_composition.pdf\",\n  \"youtube\": {\n    \"Bart\": \"7N5ch_c_3-c\",\n    \"Amy\": \"bDGw_8SYbQ8\"\n  },\n  \"summary\": \"Looping is complicated but powerful, so we want to find ways to make it easier to apply. One good solution is to compose loop functions to create more complex behavior.\",\n  \"small_layout\": true,\n  \"video\": {\n    \"Bart\": \"https://blockpy.cis.udel.edu/videos/bakery_for_composition-Bart.mp4\",\n    \"Amy\": \"https://blockpy.cis.udel.edu/videos/bakery_for_composition-Amy.mp4\"\n  }\n}","starting_code":"","subordinate":true,"tags":[],"type":"reading","url":"bakery_for_composition_read","version":8},"ip":"216.73.216.157","submission":{"_schema_version":3,"assignment_id":1081,"assignment_version":8,"attempts":0,"code":"","correct":false,"course_id":37,"date_created":"2026-05-20T14:01:45.786640+00:00","date_due":"","date_graded":"","date_locked":"","date_modified":"2026-05-20T14:01:45.786640+00:00","date_started":"","date_submitted":"","endpoint":"","extra_files":"","feedback":"","grading_status":"NotReady","id":2036911,"score":0.0,"submission_status":"Started","time_limit":"","url":"submission_url-5450cdb4-9f93-4b38-977d-b286870f4dd7","user_id":2044668,"user_id__email":"","version":0},"success":true}
