intro-to-python/05_numbers_02_exercises.ipynb
Alexander Hess e1a0dd7924 Make links look nice with images
- rename all *_00_lecture.ipynb files into *_00_content.ipynb
- add links to YouTube videos at the end of the chapters
- add links to mybinder for all chapters and exercises
- remove the old chapter 8 on mappings & sets
- update the README.md and make it look nicer
2020-04-02 16:17:54 +02:00

455 lines
15 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**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/05_numbers_02_exercises.ipynb)) to ensure that your solution runs top to bottom *without* any errors"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Chapter 5: Numbers & Bits"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Coding Exercises"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The exercises below assume that you have read [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) in the book.\n",
"\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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Discounting Customer Orders (revisited)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The \"*Volume of a Sphere*\" problem in [Chapter 2's Exercises <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_02_exercises.ipynb#Volume-of-a-Sphere) section revealed that we must consider the effects of the `float` type's imprecision.\n",
"\n",
"This becomes even more important when we deal with numeric data modeling accounting or finance data (cf., [this comment <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_so.png\">](https://stackoverflow.com/a/24976426) on \"falsehoods programmers believe about money\").\n",
"\n",
"In addition to the *inherent imprecision* of numbers in general, the topic of **[rounding numbers <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Rounding)** is also not as trivial as we might expect! [This article](https://realpython.com/python-rounding/) summarizes everything the data science practitioner needs to know.\n",
"\n",
"In this exercise, we revisit the \"*Discounting Customer Orders*\" problem from [Chapter 3's Exercises <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_02_exercises.ipynb#Discounting-Customer-Orders) section and make the `discounted_price()` function work *correctly* for real-life sales data."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1**: Execute the code cells below! What results would you have *expected*, and why?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"round(1.5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"round(2.5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"round(2.675, 2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2**: The built-in [round() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#round) function implements the \"**[round half to even <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even)**\" strategy. Describe in one or two sentences what that means!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q3**: For the revised `discounted_price()` function, we have to tackle *two* issues: First, we have to replace the built-in `float` type with a data type that allows us to control the precision. Second, the discounted price should be rounded according to a more human-friendly rounding strategy, namely \"**[round half away from zero <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)**.\"\n",
"\n",
"Describe in one or two sentences how \"**[round half away from zero <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)**\" is more in line with how humans think of rounding!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q4**: We use the `Decimal` type from the [decimal <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/decimal.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) to tackle *both* issues simultaneously.\n",
"\n",
"Assign `euro` a numeric object such that both `Decimal(\"1.5\")` and `Decimal(\"2.5\")` are rounded to `Decimal(\"2\")` (i.e., no decimal) with the [quantize() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) method!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from decimal import Decimal"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"euro = ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Decimal(\"1.5\").quantize(...)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Decimal(\"2.5\").quantize(...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q5**: Obviously, the two preceding code cells still [round half to even <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even).\n",
"\n",
"The [decimal <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/decimal.html) module defines a `ROUND_HALF_UP` flag that we can pass as the second argument to the [quantize() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) method. Then, it [rounds half away from zero <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero).\n",
"\n",
"Add `ROUND_HALF_UP` to the code cells! `Decimal(\"2.5\")` should now be rounded to `Decimal(\"3\")`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from decimal import ROUND_HALF_UP"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Decimal(\"1.5\").quantize(...)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Decimal(\"2.5\").quantize(...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q6**: Instead of `euro`, define `cents` such that rounding occurs to *two* decimals! `Decimal(\"2.675\")` should now be rounded to `Decimal(\"2.68\")`. Do *not* forget to include the `ROUND_HALF_UP` flag!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"cents = ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Decimal(\"2.675\").quantize(...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q7**: Rewrite the function `discounted_price()` from [Chapter 3's Exercises <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_02_exercises.ipynb#Discounting-Customer-Orders) section!\n",
"\n",
"It takes the *positional* arguments `unit_price` and `quantity` and implements a discount scheme for a line item in a customer order as follows:\n",
"\n",
"- if the unit price is over 100 dollars, grant 10% relative discount\n",
"- if a customer orders more than 10 items, one in every five items is for free\n",
"\n",
"Only one of the two discounts is granted, whichever is better for the customer.\n",
"\n",
"The function then returns the overall price for the line item as a `Decimal` number with a precision of *two* decimals.\n",
"\n",
"Enable **duck typing** by allowing the function to be called with various numeric types as the arguments, in particular, `quantity` may be a non-integer as well: Use an appropriate **abstract base class** from the [numbers <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/numbers.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) to verify the arguments' types and also that they are both positive!\n",
"\n",
"Hint: It is considered a *best practice* to only round towards the *end* of the calculations."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numbers"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def discounted_price(unit_price, quantity):\n",
" \"\"\"Calculate the price of a line item in an order.\n",
"\n",
" Args:\n",
" unit_price (numbers.Real): price of an ordered item; must be positive\n",
" quantity (numbers.Real): number of items ordered; must be positive\n",
"\n",
" Returns:\n",
" line_item_price (decimal.Decimal): precision of 2 decimals\n",
" \"\"\"\n",
" # Basic input validation.\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
"\n",
" # Calculate the final price if only\n",
" # the first discount scheme is applied.\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
"\n",
" # Calculate the final price if only\n",
" # the second discount scheme is applied.\n",
" # \"One in every five\" means we need to figure out\n",
" # how many full groups of five are contained.\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
"\n",
" # Choose the better option for the customer.\n",
" ...\n",
" # Round correctly for pricing purposes.\n",
" return ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q8**: Execute the code cells below and verify the final price for the following four test cases:\n",
"\n",
"- $7$ smartphones @ $99.00$ USD\n",
"- $3$ workstations @ $999.00$ USD\n",
"- $19$ GPUs @ $879.95$ USD\n",
"- $14$ Raspberry Pis @ $35.00$ USD\n",
"\n",
"The output should now *always* be a `Decimal` number with *two* decimals!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(99, 7)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(999, 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(879.95, 19)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(35, 14)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This also works if `quantity` is passed in as a `float` type."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(99, 7.0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Decimals beyond the first two are gracefully discarded (i.e., *without* rounding errors accumulating)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(99.0001, 7)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The basic input validation ensures that the user of `discounted_price()` does not pass in invalid data. Here, the `\"abc\"` creates a `TypeError`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(\"abc\", 7)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A `-1` passed in as `unit_price` results in a `ValueError`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"discounted_price(-1, 7)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": false,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}