Skip to content

Instantly share code, notes, and snippets.

@jmayse
Created July 9, 2018 22:13
Show Gist options
  • Save jmayse/c51ef3a9b78e01833d0a6709dfd3df0d to your computer and use it in GitHub Desktop.
Save jmayse/c51ef3a9b78e01833d0a6709dfd3df0d to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Control Flow & Functions ##\n",
"\n",
"Python code is executed one line at a time (in most cases). Often, we want to repeat some work many times, modifying the work slightly or drastically each time we _iterate_. To simplify & clarify this process, we use control flow statements and functions. \n",
"\n",
"Consider the following problem: You are selling tickets to a baseball game. Children (12) and seniors (65) get in free, while adults pay \\$8. For parties of more than 10 total people, the adult price is \\$7 each, and children and seniors still get in free. \n",
"\n",
"How could we write some python code to determine how much each party pays at the door?\n",
"\n",
"First, let's define our payment structure using an `if` statement.\n",
"\n",
"`if` is a keyword in Python that evaluates a statement. If that statement is `True`, Python executes the code in the block controlled by the `if` statement. If not, Python moves on.\n",
"\n",
"`if` is often combined with `else` and `elif`. When python encounters an `if` statement with a predicate that evaluates to `False`, if moves to the next statement. If this statement is an `elif` statement, Python will evaluate another conditional: if `False`, we again move on. If `True`, we execute that code block. Finally, `else` statements are always evaluated and *must* come at the end of a block. Both `else` and `elif` must be preceded by one and only one `if`."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The ticket price is $0\n"
]
}
],
"source": [
"#Consider we have an Age variable:\n",
"\n",
"age = 10\n",
"if age <= 12:\n",
" price = 0\n",
"elif age >= 65: \n",
" price = 0\n",
"else:\n",
" price = 8\n",
" \n",
"print(\"The ticket price is ${steve}\".format(steve=price))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this code, we hard-code `age` to 10 years and evaluate the `if` statement. Each statement is evaluated in order - first, we check if `age` is less than 12. It is; and so `price` is set to equal 0.\n",
"\n",
"What happens if `age` is greater than 65? What if `age` is 32?\n",
"\n",
"Is there a simpler way to write this logic (hint: under what condition can price be _nonzero_?)\n",
"\n",
"The problem posed at the start concerns _groups_ of people. This logic works well for us, but is tedious to repeat. We can use loops and functions to scale our code.\n",
"\n",
"Loops are code statememnts that repeat the same code a specific number of times, or until a certain condition is met. In Python, the two prominent loop statements are `for` and `while` statements. \n",
"\n",
"`for` executes a finite number of times, or over a finite number of objects/pieces of data. `while` executes forever until a specific condition is met, at which point it exits. \n",
"\n",
"4 people - 2 adults and 2 children - approach the gate and want tickets. We can use a `for` loop to determine their total bill."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The party's total is $16.00\n"
]
}
],
"source": [
"party_ages = [4, 6, 38, 41]\n",
"\n",
"total = 0\n",
"for party_position in range(len(party_ages)):\n",
" if party_ages[party_position] < 12:\n",
" total += 0\n",
" elif party_ages[party_position] > 65: \n",
" total += 0\n",
" else:\n",
" total += 8\n",
"\n",
"print('The party\\'s total is ${total:.2f}'.format(total=total))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, the `for` loop loops over the output of the `range` function. `range` creates a type of data structure called an `iterable` - iterables _yield_ a single value at a time often by using the `in` keyword in conjunction with `for` or `while`. \n",
"\n",
"`range` takes in at least one integer and returns a range of integers between 0 and that value (if a single integer is passed), or between the two integers (if two integers are passed). `range` also accepts a `step` argument that specifies how to increment between the `start` and `stop` arguments:"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[4, 6, 38, 41]\n"
]
}
],
"source": [
"print([party_ages[i] for i in range(4)])"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n"
]
}
],
"source": [
"print([i for i in range(20)])"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n"
]
}
],
"source": [
"print([i for i in range(0, 20, 2)])"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[20, 18, 16, 14, 12, 10, 8, 6, 4, 2]\n"
]
}
],
"source": [
"print([i for i in range(20, 0, -2)])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`len` is a built-in function that returns the number of elements in an object. `len(party_ages)` returns `4` because there are 4 elements in the list `party_ages`. We can pass `len(party_ages)` as an input to `range` because the output of the function `len` is an integer. So, `for party_position in range(len(party_ages)):` can be interpreted as \"perform the following code the same number of times as there are items in the `party_ages` list.\"\n",
"\n",
"However, Python provides a specific functionality to iterate over an iterable _and_ return an integer corresponding to an item's position: `enumerate`:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The party's total is $16. The average ticket price is $4.00\n",
"Or, we can just iterate over the list!\n",
"The party's total is $16. The average ticket price is $4.00\n"
]
}
],
"source": [
"party_ages = [4, 6, 38, 41]\n",
"\n",
"total = 0\n",
"number_of_tickets = 0\n",
"for party_position, age in enumerate(party_ages):\n",
" number_of_tickets = party_position + 1 # Why do we add 1 here?\n",
" if age < 12:\n",
" total += 0\n",
" elif age > 65: \n",
" total += 0\n",
" else:\n",
" total += 8\n",
" \n",
"\n",
"print('The party\\'s total is ${total}. The average ticket price is ${avg_price:.2f}'.format(total=total,\n",
" avg_price=(total/number_of_tickets)))\n",
"\n",
"#Alternatively, lists themselves are iterable!\n",
"print(\"Or, we can just iterate over the list!\")\n",
"party_ages = [4, 6, 38, 41]\n",
"\n",
"total = 0\n",
"for age in party_ages:\n",
" if age < 12:\n",
" total += 0\n",
" elif age > 65: \n",
" total += 0\n",
" else:\n",
" total += 8\n",
" \n",
"print('The party\\'s total is ${total}. The average ticket price is ${avg_price:.2f}'.format(total=total,\n",
" avg_price=(total/len(party_ages))))\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We've used `for` loops to iterate over a list of ages and calculate the total ticket price, but each time we've hardcoded the list. Generally, we want our programs to be more flexible. We can define a general function that solves this problem for us. A function is a block of code that can accept parameters, or arguments, and can return an output. It doesn't have to! Some functions take no arguments and return no output (that you define). \n",
"\n",
"Here, we indicate that we want to write a function by using the `def` keyword. We want to write a function that takes in a list of ages and returns a total cost."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"def cashier(list_of_ages: list) -> int:\n",
" # We include pass here because the body of a function must always have something in it, even if it does nothing.\n",
" pass \n",
"\n",
"print(\"This is not in the function\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The components of this function creation look complicated but are easy to understand once we break them apart. \n",
"\n",
"First, we indicate that the block of code we're about to write is all one function by using `def`. Python is whitespace sensitive, so the interpreter will look for code that has the same indent. As soon we unindnent a line, Python considers that block of code ended. You could have seen this in our `if` and `for` blocks - code controlled by `if` and `for` shares an indent, and code outside that control is indented lesser. \n",
"\n",
"Second, we denote a space to pass arguments or parameters to the function `cashier` with open and close parentheses `(` and `)`. `cashier` takes only one argument, which is a variable called `list_of_ages` that has the type `list`. We separate the argument name and type with a colon, and separate arguments are delimited with commas. \n",
"\n",
"Finally, we denote that the function has an output, and that output has a type `int`, or integer. Notice that unlike input arguments, output arguments do not have a name specified! That's because we assign the output(s) of a function to a variable or variables outside the scope of the function itself. This way, if we call the `cashier` function in multiple places, once per each gate that we are selling tickets at, we can separately track the different prices at the same time, in the same code. \n",
"\n",
"Let's fill in our `cashier` function with our price logic. We can use `return` to tell Python to output the total price when the function has finished running:"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The party's total is $16.\n"
]
}
],
"source": [
"def cashier(list_of_ages: list) -> int:\n",
" total = 0\n",
" for age in list_of_ages:\n",
" if age < 12:\n",
" total += 0\n",
" elif age > 65: \n",
" total += 0\n",
" else:\n",
" total += 8\n",
" return total\n",
"\n",
"print('The party\\'s total is ${total}.'.format(total=cashier([4, 6, 38, 41])))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Great, our `cashier` function works, and we can pass it an arbitrary list of one person, or a hundred people, and know their totals. However, this function still isn't general enough. Remember that we have a discount for larger parties! If there are more than 10 people in a party, we only charge \\$7 per ticket.\n",
"\n",
"How would we modify the `cashier` function to fulfill this requirement? "
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The smaller party's total is $16.\n",
"The larger party's total is $35.\n"
]
}
],
"source": [
"def cashier(list_of_ages: list) -> int:\n",
" total = 0\n",
" if len(list_of_ages) > 9:\n",
" full_price = 7\n",
" else:\n",
" full_price = 8\n",
" \n",
" for age in list_of_ages:\n",
" if age < 12:\n",
" total += 0\n",
" elif age > 65: \n",
" total += 0\n",
" else:\n",
" total += full_price\n",
" return total\n",
"\n",
"print('The smaller party\\'s total is ${total}.'.format(total=cashier([4, 6, 38, 41])))\n",
"\n",
"print('The larger party\\'s total is ${total}.'.format(total=cashier([4, 6, 38, 41, 10, 22, 67, 33, 2, 18])))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we're getting somewhere, but it's hard to tell if our function is working without looking at the list of ages. What if we print the ticket price on the tickets? We can modify our `cashier` function to return us additional data: the ticket price paid. "
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The smaller party's total is $16. They paid $8 per ticket\n",
"The larger party's total is $35. They paid $7 per ticket\n"
]
}
],
"source": [
"def cashier(list_of_ages: list) -> (int, int):\n",
" total = 0\n",
" if len(list_of_ages) > 9:\n",
" full_price = 7\n",
" else:\n",
" full_price = 8\n",
" \n",
" for age in list_of_ages:\n",
" if age < 12:\n",
" total += 0\n",
" elif age > 65: \n",
" total += 0\n",
" else:\n",
" total += full_price\n",
" return total, full_price\n",
"\n",
"total_price, ticket_price = cashier([4, 6, 38, 41])\n",
"print('The smaller party\\'s total is ${total}. They paid ${ticket_price} per ticket'.format(total=total_price,\n",
" ticket_price=ticket_price))\n",
"total_price, ticket_price = cashier([4, 6, 38, 41, 10, 22, 67, 33, 2, 18])\n",
"print('The larger party\\'s total is ${total}. They paid ${ticket_price} per ticket'.format(total=total_price,\n",
" ticket_price=ticket_price))\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We've modified our `cashier` function to return two numbers instead of one. Notice the changes that we've made at the declaration of the function and in the `return` statement: we added `(int, int)` after the `->` output type statement and added the `full_price` variable when we return. Python functions return _tuples_, which, like `lists`, contain a number of values separated by commas. However, unlike a `list`, a `tuple` is _immutable_, meaning that it contains a fixed number of values that cannot be changed. In addition, tuples are most often used with _unpacking_, where the elements of an iterable are separated. \n",
"\n",
"As a bonus note on functions & unpacking - dictionaries can also be unpacked into keyword-value pairs, and passed as function arguments:"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The smaller party's total is $16. They paid $8 per ticket\n",
"The larger party's total is $35. They paid $7 per ticket\n"
]
}
],
"source": [
"def cashier(list_of_ages: list) -> (int, int):\n",
" total = 0\n",
" if len(list_of_ages) > 9:\n",
" full_price = 7\n",
" else:\n",
" full_price = 8\n",
" \n",
" for age in list_of_ages:\n",
" if age < 12:\n",
" total += 0\n",
" elif age > 65: \n",
" total += 0\n",
" else:\n",
" total += full_price\n",
" return {'total':total, 'ticket_price':full_price}\n",
"\n",
"output_dict = cashier([4, 6, 38, 41])\n",
"print('The smaller party\\'s total is ${total}. They paid ${ticket_price} per ticket'.format(total=output_dict['total'],\n",
" ticket_price=output_dict['ticket_price']))\n",
"\n",
"print('The larger party\\'s total is ${total}. They paid ${ticket_price} per ticket'.format(**cashier([4, 6, 38, 41, 10, 22, 67, 33, 2, 18])))\n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python [conda root]",
"language": "python",
"name": "conda-root-py"
},
"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.6.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment