{"assignment":{"_schema_version":2,"course_id":37,"date_created":"2022-06-28T19:00:00+00:00","date_modified":"2023-09-10T02:20:09.850605+00:00","extra_instructor_files":"","extra_starting_files":"","forked_id":null,"forked_version":null,"hidden":false,"id":1031,"instructions":"## Logical Patterns\n\n- And vs. Nested If\n- Defensive Guard\n- Early Return\n- Multiple Return Spots\n- Build-Up-Return\n- Define-and-Refine\n\nSince `if` statements are so useful, they can be a little overwhelming when you get started.\nThis lesson gives some examples of patterns that can help guide your thinking about using `if` statements in different ways.\nThese patterns are not officially part of the Python language.\nThey are merely templates that we have created based on common patterns we see in code.\nThey are not exhaustive since there are many ways to leverage and twist conditionals.\nBut if you treat them as starting points, you may find that they inspire you in different directions.\n\n## And vs. Nested If\n\n```python\nif __A__ and __B__:\n    # ...\n\n# Is the same as...\n\nif __A__:\n    if __B__:\n        # ...\n```\n\nAn `if` statement nested inside of an `if` statement is equivalent to having both conditions combined with an `and` statement.\nThe advantage of an `and` expression is usually brevity since the code will only take up a single line without an extra indentation.\nThe advantage of the nested `if` statement, however, is a little more nuanced.\nStatements can be added before or after the inner `if` statement, allowing more statements for the first case but not necessarily for the second.\nYou can also specify additional cases' behavior by adding in `elif` and `else` statements to the inner `if` statements if you need to narrow the behavior further in the case where the first condition is true and the second condition is false.\n\n## Example: And vs. Nested If\n\n```python example-and-nested-if\nif weather.lower() == 'sunny' and temperature > 70:\n    print(\"Warm!\")\nif weather.lower() == 'sunny' and temperature < 30:\n    print(\"Cold!\")\n\n# Is the same as...\n\nif weather.lower() == 'sunny':\n    if temperature > 70:\n        print(\"Warm!\")\n    if temperature < 30:\n        print(\"Cold!\")\n```\n\nAs a concrete example, observe this code.\nThe first version has to have redundant versions of the `weather.lower()` check.\nThe second version has an extra line of code because of the second `if` statement.\nJust because a program takes fewer lines of code does not mean that that solution is more readable; with less redundant logic, the second block of code is less likely to have typos once you get used to reading nested `if` statements.\n\n## Defensive Guard\n\n```python\nif ___:\n    # Potentially unsafe operation\n```\n\nThere are operations and functions which are valid syntactically but will cause an error in practice.\nThe Defensive Guard pattern is just the concept of \"protecting\" those operations from occurring by first checking that the data involved is valid.\nThe pattern is simple, just literally an `if` statement. But the concept has nuance.\n\n## Example: Defensive Guard\n\n```python defensive-guard-numerics\n# Guard against division by zero\nif time > 0:\n    speed = distance / time\n\n# Guard against bad conversion\nif user_input.isdigits():\n    age = int(user_input)\n```\n\nA classic example is division by zero, which causes a `ZeroDivisionError`.\nUsing an `if` statement, you can make sure that a variable such as `time` is greater than zero and therefore safe to use for the denominator.\n\nAs another example, you can use the `isdigits` method of strings to make sure that the string is all numbers before you try to convert it to a `float` or an `integer.\n\n## More Examples: Defensive Guard\n\n```python defensive-guard-indexing\n# Truthiness to test if there are ANY characters\nif user_input:\n    last_character = user_input[-1]\n    first_character = user_input[0]\n\n# Check length to make sure that there are at least three characters\nif len(user_input) >= 3:\n    third_character = user_input[2]\n```\n\nOne other common check is testing the length of a string before you index a specific character.\nIf you want the first or last character, then you must determine that the string is non-empty.\nThis can be easily done using truthiness.\nHowever, if you want a specific inner character, you must check that the string is at least that long.\n\n## Early Return\n\n```python\n# Early Return\ndef function(___):\n    if ___:\n        return ___\n    # do something ...\n    return ___\n```\n\nThe Early Return pattern is like the Defensive Guard pattern, with an `if` statement ending a branch inside of a function before an error (or other undesirable operation) can occur.\n\n## Example of Early Return\n\n```python early-return\ndef get_punctuation(message: str) -> str:\n    if not message:\n        return \"empty string\"\n    last_character = message[-1]\n    if last_character == \"?\":\n        return \"question\"\n    elif last_character == \".\":\n        return \"statement\"\n    return \"other\"\n```\n\nIn the example shown here, we check if the `message` parameter does not have a truthy value.\nFor a string, any non-empty string will be considered truthy.\nIn this case, then, indexing is only attempted if the `message` is not empty.\nThis is very important, since indexing the last character of the string will cause an `IndexError` if the empty string was indexed.\nThe early return prevents the mistake from occurring, allowing us to safely use the logic in the rest of the function to determine what kind of punctuation symbol the last character of the string holds.\n\n## Multiple Return Spots\n\n```python\ndef function(___):\n    if ___:\n        return ___\n    elif ___:\n        return ___\n    # ...\n    else:\n        return ___\n```\n\nParsing code with multiple returns can be a little tricky.\nWhat gets returned? The key thing to remember is that _a function can only return exactly once_.\nThat will happen regardless of whether you have a `return` statement.\nBut if a `return` statement is encountered, the function is over, and whatever value is specified in the `return` statement is what is brought back to the original call site.\nThat's why having more than one `return` statement only makes sense when you have `if` statements.\nThe `if` statements can disrupt the control flow of the function, branching off different paths for each `return` statement.\nFor each call to the function, only one path needs to be taken, with only one `return` statement being activated.\n\n## Example: Multiple Return Spots\n\n```python convert-ordinal-multiple\ndef convert_ordinal(number: int) -> str:\n    if number == 1:\n        return \"first\"\n    elif number == 2:\n        return \"second\"\n    elif number == 3:\n        return \"third\"\n    else:\n        return \"other\"\n```\n\nIn this example, the `convert_ordinal` function consumes an integer and produces a string representation of the ordinal number such as \"first,\" \"second,\" or \"third.\" The value stored in `number` is matched against each of the possibilities in turn, but only one can be true since we are using a mutually exclusive chain of `if`/`elif`/`else` statements.\nUltimately, if none of the expressions evaluate to `True`, then the `else` block is entered and the value `\"other\"` is returned.\n\n## Build-Up-Return Pattern\n\n```python\ndef function(___):\n    if ___:\n        result = ___\n    elif ___:\n        result = ___\n    # ...\n    else:\n        result = ___\n    return result\n```\n\nA related pattern is the Build-Up-Return pattern.\nHow is this different from the Multiple Return Spots pattern? Functionally, they are equivalent.\nThe value being returned is meant to be the same in either pattern.\nThe difference comes in the number of `return` statements, and the exact path that the control flow takes through the program.\n\nSome folks try to avoid having multiple return spots in their program since it is a common source of mistakes in beginner programs.\nFor those struggling with control flow, it can make a lot of sense to avoid trying to mix the confusing branching of `if` statements with the abrupt finality of `return` statements.\nThe Build-Up-Return Pattern can help you isolate the `return` to a single spot, making it easier to think about.\n\n## Example: Build-Up-Return Pattern\n\n```python convert-ordinal-build-up\ndef convert_ordinal(number: int) -> str:\n    if number == 1:\n        result = \"first\"\n    elif number == 2:\n        result = \"second\"\n    elif number == 3:\n        result = \"third\"\n    else:\n        result = \"other\"\n    return result\n```\n\nIn the previous `convert_ordinal` pattern, we had `return` statements inside of the `if` statements.\nBut now, there is only one `return` after all the conditionals.\nInstead, assignment statements are used.\nThe result is still the same, though.\n\n## Define-and-Refine Pattern\n\n```python\nif ___:\n    if ___:\n        value = __special__\n    else:\n        value = __default__\nelse:\n    value = __default__\n\n# Becomes\n\nvalue = __default__\nif ___:\n    if ___:\n        value = __special__\n```\n\nThe `else` statement is a powerful way to specify default behavior after `if` and `elif` statements.\nBut, when nesting these statements, the `else` branch is not always conveniently available.\nThe nested statements represent specific, narrow cases involving multiple conditions.\nTo provide a default situation with `else` statements, every single level of nested `if` would need to have an `else` statement, sometimes leading to redundant code.\nHowever, the Define-and-Refine pattern allows you to specify an initial value for a variable and then only specify the branches to change that value.\n\n## Opportunity for Define-and-Refine Pattern\n\n```python bad-define-refine\n# Messy!\nhour = int(input(\"What hour is it?\"))\nday = input(\"What day is it?\")\n\nif day == \"Saturday\" or day == \"Sunday\":\n    if hour < 12:\n        can_sleep_in = True\n    else:\n        can_sleep_in = False\nelse:\n    can_sleep_in = False\n\nprint(\"Can sleep in?\", can_sleep_in)\n```\n\nIn this code example, the program is attempting to determine whether the user can sleep in.\nIt must not only be a weekday, but it must also be before noon.\nHowever, with the way the code is currently structured, we must provide an `else` body for each of the conditionals.\nThis is inconvenient since the action to be taken (assigning `False` to `can_sleep_in`) is the same along both of those paths.\nThe Define-and-Refine Pattern suggests a solution to clean up this code.\n\n## Example after Define-and-Refine Pattern\n\n```python good-define-refine\n# Cleaner\nhour = int(input(\"What hour is it?\"))\nday = input(\"What day is it?\")\n\ncan_sleep_in = False\nif day == \"Saturday\" or day == \"Sunday\":\n    if hour > 12:\n        can_sleep_in = True\n\nprint(\"Can sleep in?\", can_sleep_in)\n```\n\nIn the new version, we first initialize the `can_sleep_in` variable before we structure the `if` logic chain.\nThat way, we no longer require any `else` paths at all.\nThis only works because the behavior was the same along all those paths, though.\nWe couldn't have made this change if either path had different behavior.\nGranted, both of these programs also ignore the many good people who sleep in past noon, but let us leave them aside for the moment.\n\nSometimes there is readability value in having an explicit `else` case, so that future developers will understand that you meant two cases to be distinct from each other.\nWith every one of these patterns, you must think critically about how you can make the code as readable as possible, and whether the pattern improves the code or not.\n\n## Summary\n\nThere are many ways to use `if` statements to improve readability and control your code's flow:\n\n- The And vs. Nested `if` pattern observes that nesting `if` statements is functionally equivalent to chaining together `and` operators.\n- The Defensive Guard pattern suggests using an `if` statement to avoid errors.\n- The Early Return pattern is like the Defensive Guard, but specifically used inside of function definitions to terminate the function prematurely.\n- The Multiple Return Spots pattern explains how to structure code so that you can return a different value based on function arguments.\n- The Build-Up-Return pattern is an alternative version of the Multiple Return Spots pattern that avoids having multiple `return` statements by using assignment statements instead.\n- The Define-and-Refine pattern suggests initializing a default value for a variable instead of using repeated `else` statements for a complex conditional.\n\n","ip_ranges":"","name":"3B2) Logical Patterns 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  \"small_layout\": true,\n  \"header\": \"Logical Patterns\",\n  \"youtube\": {\n    \"Bart\": \"3R6qVpSpxEE\",\n    \"Amy\": \"1PHKlrb6Swo\"\n  },\n  \"video\": {\n    \"Bart\": \"https://blockpy.cis.udel.edu/videos/bakery_if_patterns-Bart.mp4\",\n    \"Amy\": \"https://blockpy.cis.udel.edu/videos/bakery_if_patterns-Amy.mp4\"\n  },\n  \"slides\": \"bakery_if_patterns.pdf\"\n}","starting_code":"","subordinate":true,"tags":[],"type":"reading","url":"bakery_if_patterns_read","version":8},"ip":"216.73.216.157","submission":{"_schema_version":3,"assignment_id":1031,"assignment_version":8,"attempts":0,"code":"","correct":false,"course_id":37,"date_created":"2026-05-20T14:01:39.027055+00:00","date_due":"","date_graded":"","date_locked":"","date_modified":"2026-05-20T14:01:39.027055+00:00","date_started":"","date_submitted":"","endpoint":"","extra_files":"","feedback":"","grading_status":"NotReady","id":2036896,"score":0.0,"submission_status":"Started","time_limit":"","url":"submission_url-b3de6e11-4d06-4e3b-a36e-7928818f8109","user_id":2044668,"user_id__email":"","version":0},"success":true}
