"**Important**: Click on \"*Kernel*\" > \"*Restart Kernel and Run All*\" *after* finishing the exercises in [JupyterLab <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_jp.png\">](https://jupyterlab.readthedocs.io/en/stable/) (e.g., in the cloud on [MyBinder <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_mb.png\">](https://mybinder.org/v2/gh/webartifex/intro-to-python/master?urlpath=lab/tree/07_sequences_02_exercises.ipynb)) to ensure that your solution runs top to bottom *without* any errors"
"The exercises below assume that you have read [Chapter 7 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb) in the book.\n",
"The `...`'s in the code cells indicate where you need to fill in code snippets. The number of `...`'s within a code cell give you a rough idea of how many lines of code are needed to solve the task. You should not need to create any additional code cells for your final solution. However, you may want to use temporary code cells to try out some ideas."
"**Q1.1**: Write a function `nested_sum()` that takes a `list` object as its argument, which contains other `list` objects with numbers, and adds up the numbers! Use `nested_numbers` below to test your function!\n",
"**Q1.2**: Generalize `nested_sum()` into a function `mixed_sum()` that can process a \"mixed\" `list` object, which contains numbers and other `list` objects with numbers! Use `mixed_numbers` below for testing!\n",
"Hints: Use the built-in [isinstance() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#isinstance) function to check how an element is to be processed. Get extra credit for adhering to *goose typing*, as explained in [Chapter 5 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers_00_content.ipynb#Goose-Typing): Use some appropriate abstract base class (ABC) from the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html)."
"**Q1.3.1**: Write a function `cum_sum()` that takes a `list` object with numbers as its argument and returns a *new* `list` object with the **cumulative sums** of these numbers! So, `sum_up` below, `[1, 2, 3, 4, 5]`, should return `[1, 3, 6, 10, 15]`.\n",
"Hint: The idea behind is similar to the [cumulative distribution function <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Cumulative_distribution_function) from statistics."
"**Q1.3.2**: We should always make sure that our functions also work in corner cases. What happens if your implementation of `cum_sum()` is called with an empty list `[]`? Make sure it handles that case *without* crashing! What would be a good return value in this corner case?\n",
"In the \"*Function Definitions & Calls*\" section in [Chapter 7 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Function-Definitions-&-Calls), we define the following function `product()`. In this exercise, you will improve it by making it more \"user-friendly.\""
"Because of the packing, we may call `product()` with an abitrary number of *positional* arguments: The product of just `42` remains `42`, while `2`, `5`, and `10` multiplied together result in `100`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(42)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(2, 5, 10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, \"abitrary\" does not mean that we can pass *no* argument. If we do so, we get an `IndexError`."
"In [Chapter 7 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Function-Definitions-&-Calls), we also pass a `list` object, like `one_hundred`, to `product()`, and *no* exception is raised."
"**Q2.3**: What is wrong with that? What *kind* of error (cf., [Chapter 1 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_content.ipynb#Formal-vs.-Natural-Languages)) is that conceptually? Describe precisely what happens to the passed in `one_hundred` in every line within `product()`!"
"**Q2.6**: Replace the `None` in `product()` above with something reasonable that does *not* cause exceptions! Ensure that `product(42)` and `product(2, 5, 10)` return a correct result.\n",
"Hints: It is ok if `product()` returns a result *different* from the `None` above. Look at the documentation of the built-in [sum() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sum) function for some inspiration."
"Now, calling `product()` without any arguments returns what we would best describe as a *default* or *start* value. To be \"philosophical,\" what is the product of *no* numbers? We know that the product of *one* number is just the number itself, but what could be a reasonable result when multiplying *no* numbers? The answer is what you use as the initial value of `result` above, and there is only *one* way to make `product(42)` and `product(2, 5, 10)` work."
"**Q2.7**: Rewrite `product()` so that it takes a *keyword-only* argument `start`, defaulting to the above *default* or *start* value, and use `start` internally instead of `result`!\n",
"Hint: Remember that a *keyword-only* argument is any parameter specified in a function's header line after the first (and only) `*` (cf., [Chapter 2 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions_00_content.ipynb#Keyword-only-Arguments))."
"Now, we can call `product()` with a truly arbitrary number of *positional* arguments."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(42)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(2, 5, 10)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Without any *positional* arguments but only the *keyword* argument `start`, for example, `start=0`, we can adjust the answer to the \"philosophical\" problem of multiplying *no* numbers. Because of the *keyword-only* syntax, there is *no* way to pass in a `start` number *without* naming it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(start=0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We could use `start` to inject a multiplier, for example, to double the outcomes."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(42, start=2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(2, 5, 10, start=2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There is still one issue left: Because of the function's name, a user of `product()` may assume that it is ok to pass a *collection* of numbers, like `one_hundred`, which are then multiplied."
"**Q2.9**: Rewrite the latest version of `product()` to check if the *only* positional argument is a *collection* type! If so, its elements are multiplied together. Otherwise, the logic remains the same.\n",
"Hints: Use the built-in [len() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#len) and [isinstance() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#isinstance) functions to check if there is only *one* positional argument and if it is a *collection* type. Use the *abstract base class* `Collection` from the [collections.abc <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/collections.abc.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html). You may want to *re-assign* `args` inside the body."
"**Side Note**: Above, we make `product()` work with a single *collection* type argument instead of a *sequence* type to keep it more generic: For example, we can pass in a `set` object, like `{2, 5, 10}` below, and `product()` continues to work correctly. The `set` type is introducted in [Chapter 9 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/09_mappings_00_content.ipynb#The-set-Type), and one essential difference to the `list` type is that objects of type `set` have *no* order regarding their elements. So, even though `[2, 5, 10]` and `{2, 5, 10}` look almost the same, the order implied in the literal notation gets lost in memory!"
"Let's continue to improve `product()` and make it more Pythonic. It is always a good idea to mimic the behavior of built-ins when writing our own functions. And, [sum() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sum), for example, raises a `TypeError` if called *without* any arguments. It does *not* return the \"philosophical\" answer to adding *no* numbers, which would be `0`."