{
"cells": [
{
"cell_type": "markdown",
"id": "adeab882-0b7a-4e03-aeeb-2a0d3d621d5f",
"metadata": {},
"source": [
"# PE100-03: Decision Structures\n",
"\n",
"In the first lesson, everything we did was sequential programming. Statements are executed one after the other in exactly the order they're written in. As long as there aren't any errors, every statement will be executed.\n",
"\n",
"## The Simplest \"if\" Statement\n",
"\n",
"In almost any real Jupyter notebook or standalone program we write, there will have to be places where different code paths are taken depending on what has happened leading up to there. Suppose we're looking at absorption at one specific wavelength and we know that some of our instruments are a little bit too sensitive to changes in humidity. Maybe the first spectrometer has some insulation that is just a little too porous and reads a bit high, but the second one is even worse. We have calibration constants we can apply, but we have to apply the right constant for each individual instrument."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b5963a34-f762-437f-b5f9-300015133d44",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"7.539441569999999"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spectrometer_number = 1 \n",
"reading = 7.00041 \n",
" \n",
"if spectrometer_number == 1: \n",
" useful_result = reading * 1.077 \n",
" \n",
"useful_result\n"
]
},
{
"cell_type": "markdown",
"id": "c21dcc06-0a92-42ae-9237-32c96eeaefaf",
"metadata": {},
"source": [
"Here we have the first Decision Structure (also called *control flow statement*) that we'll look at. Taking the above code apart, we see\n",
"several important things. \n",
" \n",
"1. This is an \"if statement\".\n",
"1. Testing to see if two things are equal is done with **two** equals\n",
"signs, not one (```==```). There's a historical reason for this, and\n",
"it's a good reason, but it *always* trips up newcomers. You have been\n",
"warned. You're welcome.\n",
"1. The last character on the ```if``` line is ```:``` (a colon ).\n",
"1. The \"body\" of the ```if``` statement, the part that is run if and only\n",
"if the tested condition is met, is indented.\n",
"\n",
"In the case of the above ```if``` statement, what the code does is\n",
"check to see if we're using spectrometer number 1 and if we are then\n",
"we add 7.7% to the reading and save it in a variable called\n",
"\"useful_result\".\n",
"\n",
"## Else: the catch-all specialist\n",
"\n",
"If that was all an ```if``` statement could do then it would be really\n",
"useful. But that's not all it can do. We need to do something\n",
"reasonable when we get readings from the second instrument. Such as:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "14ddbfc7-25f4-430e-b184-693dd9d85ad5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"8.3304879"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spectrometer_number = 2 \n",
"reading = 7.00041 \n",
" \n",
"if spectrometer_number == 1: \n",
" useful_result = reading * 1.077 \n",
"else: \n",
" useful_result = reading * 1.19\n",
"\n",
"useful_result"
]
},
{
"cell_type": "markdown",
"id": "7e7c9bc4-50b2-459b-9b0b-55e83dd57c92",
"metadata": {},
"source": [
"Here we have added an \"else clause\".\n",
"The above code is interpreted as \"check to see if\n",
"we're using spectrometer number 1 and if we are then we add 7.7% to\n",
"the reading and save it in a variable called useful_result. Otherwise,\n",
"set useful_result to whatever is saved in \"reading\" plus 19%.\n",
"\n",
"So far, so good. But there's more! Suppose we need to handle several\n",
"of these not-quite-top-quality spectrometers. How do you suppose we could deal\n",
"with that? We could resort to putting if-else statements inside if-else statements in sort of a brute force fashion..."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "395a880a-8aaf-4aad-a40c-d10c301d0dda",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.4403771999999995"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spectrometer_number = 3 \n",
"reading = 7.00041 \n",
" \n",
"if spectrometer_number == 1: \n",
" useful_result = reading * 1.077 \n",
"else: \n",
" if spectrometer_number == 2: \n",
" useful_result = reading * 1.19 \n",
" else: \n",
" if spectrometer_number == 3: \n",
" useful_result = reading * .92 \n",
" \n",
"useful_result"
]
},
{
"cell_type": "markdown",
"id": "f03a0082-414e-418b-8604-d3dc9f7578e0",
"metadata": {},
"source": [
"The above code looks a little intimidating, but all there is to it is\n",
"just a series of ```if``` statements. The logic of it goes like this:\n",
"\"If the instrument number is 1, then adjust it 7.7% and we're\n",
"done. Otherwise, it must be some other instrument number, so run our\n",
"else clause\". Then in the else clause, it does the same thing, except\n",
"checking for the second instrument and adjusting by 19%. If there was\n",
"nothing to do there (because the instrument number was 3) then we run the\n",
"```else``` clause of that second if statement. This else clause houses an\n",
"```if``` statement that checks to see if the instrument is number\n",
"three. This time it is, so the body of the if statement\n",
"is executed. We set useful_reading equal to 92% of reading.\n",
"\n",
"## Elif\n",
"\n",
"This is fine if we only have three instruments, but what do we\n",
"do if we have 20 of them? We could, in principle, type in 60 lines of code, but that\n",
"would be tedious, error prone, and would take a while to read and find\n",
"any mistakes. *Of course* there's a better way.\n",
"\n",
"That better way is the \"elif\" keyword.\n",
"\n",
"Let's see an example with 5 instruments...\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "701cf657-e57a-4bb9-9694-bed198d65f6a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"7.210422299999999"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spectrometer_number = 4 \n",
"reading = 7.00041 \n",
" \n",
"if spectrometer_number == 1: \n",
" useful_result = reading * 1.077 \n",
"elif spectrometer_number == 2: \n",
" useful_result = reading * 1.19 \n",
"elif spectrometer_number == 3: \n",
" useful_result = reading * .92 \n",
"elif spectrometer_number == 4: \n",
" useful_result = reading * 1.03 \n",
"elif spectrometer_number == 5: \n",
" useful_result = reading * 1.26 \n",
"else: \n",
" useful_result = reading \n",
" print(\"Be careful!\") \n",
" \n",
"useful_result"
]
},
{
"cell_type": "markdown",
"id": "1d95c332-4b18-4772-a2d6-d9d00dfe5d6b",
"metadata": {},
"source": [
"The final ```else``` clause is the one that runs **if no other clauses\n",
"ran**. If no clause's conditional statement is true so no clause runs, whether it's the if clause or any of the elif\n",
"clauses, then the else clause runs. It's really easy to spot\n",
"```else``` clauses even from across the room - they're the ones that\n",
"don't have a conditional test.\n",
"\n",
"Note that the ```if```, ```elif```, and ```else``` lines **must** end\n",
"with a colon. True confession time: I forget the colons about\n",
"half the time. Python catches it as an error, I fix it, and life goes on.\n",
"\n",
"## Slightly More Complicated\n",
"\n",
"You can run more than one line of code in response to the tested\n",
"conditions, but they have to be indented the same amount:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "0f7bc533-12da-4601-911b-e70b5368ad48",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7.00041 True\n"
]
}
],
"source": [
"spectrometer_number = 103\n",
"reading = 7.00041\n",
"\n",
"if spectrometer_number == 1:\n",
" useful_result = reading * 1.077\n",
" trustworthy = False\n",
"elif spectrometer_number == 2:\n",
" useful_result = reading * 1.19\n",
" trustworthy = False\n",
"else:\n",
" useful_result = reading\n",
" trustworthy = True\n",
"\n",
"print(useful_result, trustworthy)\n"
]
},
{
"cell_type": "markdown",
"id": "103aa905-d274-45a4-a00d-011e04815f38",
"metadata": {},
"source": [
"There are four interesting things going on here. The first and most important thing to notice is that we've got more than one line of code running in response to an \"if\", \"elif\", or \"else\" clause. A collection of lines that should be run together as a whole is called a *code block*. Unlike many languages that mark the start and end of code blocks with special words or characters, Python just does it by using indentation. Everything that is indented the same amount is considered to be in the same code block. We'll look at this in more detail in a few minutes.\n",
"\n",
"Secondly, we've\n",
"added lines to set a variable named \"trustworthy\" to a value depending\n",
"on whether we had to adjust the reading. Evidently, if we have to\n",
"compensate for old, dry, cracking insulators then we don't really\n",
"trust the instrument.\n",
"\n",
"The third interesting thing is the values ```True``` and\n",
"```False```. These are \"Boolean\" values, and when we put them into the\n",
"\"trustworthy\" variable then it takes on the Boolean type. There are\n",
"only two values, ```True``` and ```False```. The capitalization is\n",
"important.\n",
"\n",
"The fourth thing to notice is that we're sending two values into the\n",
"print statement and it's printing both of them. In general, we can\n",
"give the print statement any number of *arguments*, separated by\n",
"commas, and it will print all of them separated by one space.\n",
"\n",
"## Conditional (aka Relational) Operators\n",
"\n",
"The conditional test in each part of an ```if``` statement is an\n",
"expression that results in a Boolean value. So far, the only\n",
"*conditional operator* (or *relational operator*) we've seen is\n",
"```==```. There are others, though. For the sake of completeness, I'll\n",
"include ```==``` here:\n",
"\n",
"|operator|tested condition|\n",
"|--------|----------------|\n",
"|```==```| equals|\n",
"|```!=```| not equals|\n",
"|```>``` | greater than|\n",
"|```>=```| greater than or equal|\n",
"|```<``` | less than|\n",
"|```<=```| less than or equal|\n",
"\n",
"\"Relational\" has at least two meanings in computing. Relational Operators have *nothing* to do with Releational Databases.\n",
"\n",
"#### Try This\n",
"\n",
"For each of the following code cells, decide what the result is, run\n",
"the cell, and see how you did:\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "c716104a-9a9e-4a47-8453-8a6fba0af06b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"5 < 6"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e443aea6-e1bc-4be5-a2ee-1c63c94fae8a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"5.99 == 5.99"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "33bca567-b087-4125-817e-c78a346cc655",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"5 != 5.00"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "de96f5ef-2981-4eac-b722-5ceaa980d280",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"5+6 < 11"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "698be490-d6dd-442e-98d7-e2092498f06e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"6 * 6 > 12 + 12 + 12"
]
},
{
"cell_type": "markdown",
"id": "c85d4e7c-b29e-42f0-8b5c-a9455976a909",
"metadata": {},
"source": [
"Relational operators also work with strings."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "a41f56ff-4d33-4512-a73a-429899136c1a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"equals Alice.\n",
"The person is not Bob.\n",
"Alice comes before Bob in alphabetical order.\n",
"Alice comes before or in the same place as Alice in sorted order\n",
"Working left to right, the M, the a, and the r match on\n",
"both strings, but when we finally get to the y and the k, y comes\n",
"after k in alphabetical order.\n"
]
}
],
"source": [
"name = \"Alice\"\n",
"if name == \"Alice\":\n",
" print(\"equals Alice.\")\n",
"if name != \"Bob\":\n",
" print(\"The person is not Bob.\")\n",
"if \"Alice\" < \"Bob\":\n",
" print(\"Alice comes before Bob in alphabetical order.\")\n",
"if \"Alice\" <= \"Alice\":\n",
" print(\"Alice comes before or in the same place as Alice in sorted order\")\n",
"if \"Mary\" > \"Mark\":\n",
" print('Working left to right, the M, the a, and the r match on')\n",
" print('both strings, but when we finally get to the y and the k, y comes')\n",
" print('after k in alphabetical order.')"
]
},
{
"cell_type": "markdown",
"id": "685230ba-e05b-4d78-8896-fa58065780a8",
"metadata": {},
"source": [
"A couple words of caution: the comparisons are based on the ASCII codes for\n",
"each character. The \"A\" in ASCII stands for \"American\", and as you\n",
"might expect that means it only works for English language text. If\n",
"you need to handle other languages, even potentially, then there is a\n",
"better way to do it and we'll see that in the lesson on strings.\n",
"\n",
"Also, Capital letters are always less than lowercase letters, and not\n",
"in the way you might think. \"A\" is less than \"Z\", as you might expect,\n",
"but \"Z\" is greater than \"a\". The numbers 0-9 are the lowest of\n",
"all. Punctuation is sprinkled around and the only way to know for sure\n",
"is to look up \"ASCII Chart\".\n",
"\n",
"## Code Blocks\n",
"\n",
"Let's go back to that part about running several lines of code but\n",
"they have to be indented the same amount. Python always runs \"blocks\"\n",
"of code. That block might be as short as one line:\n"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "27181936-7635-4c39-a86b-8a6fd3343170",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"125.6636"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"circumference = 40 * 3.14159\n",
"\n",
"circumference"
]
},
{
"cell_type": "markdown",
"id": "7618bab7-e41a-4301-b4bd-97bb096c75e7",
"metadata": {},
"source": [
"or it might be arbitrarily long:\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "1fbb2f6f-259b-442d-a896-5cfe7bdd8857",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1511396.1762899999"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"height = 6.01\n",
"length = 5.5\n",
"width = 14.3\n",
"density = 4.2\n",
"volume = height * width * length\n",
"mass = volume * density\n",
"energy_per_gram = 761.3\n",
"eyebrow_altering_potential = mass * energy_per_gram\n",
"\n",
"eyebrow_altering_potential"
]
},
{
"cell_type": "markdown",
"id": "cdbef9aa-0396-4ac4-8c8b-a9948e3d5518",
"metadata": {},
"source": [
"Whether it was the one line example or the eight line one, Python will\n",
"set out to run all of those lines in one shot, and as long as there\n",
"aren't any errors it'll do it. These are known as *code blocks*.\n",
"\n",
"The decision structures (again, also called *control flow statements*)\n",
"in Python all do basically the same thing: they evaluate an expression\n",
"and depending on whether it turns out True or False, they execute a\n",
"code block in some manner. This means that wherever we can have a\n",
"single line of code running in a decision structure we can have as\n",
"many lines as we want.\n",
"\n",
"Take a look at the following example. For the four possible\n",
"combinations of ```potentially_hazardous``` and ```explody```, decide\n",
"what would be printed out. Then try out the combinations and make sure\n",
"you know why each combination was handled the way it was."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "dafda872-a797-43b1-a75b-9288d6824832",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total Available Kaboom (TAK) to ruin your day is 1511396.1762899999\n"
]
}
],
"source": [
"potentially_hazardous = True\n",
"explody = True\n",
"\n",
"if potentially_hazardous and explody:\n",
" height = 6.01\n",
" length = 5.5\n",
" width = 14.3\n",
" density = 4.2\n",
" volume = height * width * length\n",
" mass = volume * density\n",
" energy_per_gram = 761.3\n",
" eyebrow_altering_potential_energy = mass * energy_per_gram\n",
" print(\"Total Available Kaboom (TAK) to ruin your day is\", eyebrow_altering_potential_energy)\n",
"elif potentially_hazardous:\n",
" print(\"Not likely to go 'kaboom', but not something you want to casually eat, either.\")\n",
" print(\"I mean, unless you're feeling brave.\")\n",
" print(\"Even then, it's a bad idea.\")\n",
"elif explody:\n",
" print(\"This is one of those things that will blow up but isn't actually hazardous.\")\n",
" print(\"I'm guessing it's a vinegar-and-baking-soda volcano.\")\n",
"else:\n",
" print(\"As far as we know, the material in quesion is no more\")\n",
" print(\"dangerous than takeout pizza.\")\n"
]
},
{
"cell_type": "markdown",
"id": "150d6c0c-1de6-4a71-afde-c411aebe6db1",
"metadata": {},
"source": [
"Did you notice `potentially_hazardous and explody`? ```and```\n",
"is a **boolean operator**. We've seen the arithmetic operators already\n",
"(```+```, ```-```, ```*```, ```/```, etc.) and now here are the\n",
"boolean operators. They're named after Boolean algebra, the algebra of\n",
"logic, and are used to make larger logical expressions from smaller\n",
"ones. There are three boolean operators: ```and```, ```or```, and\n",
"```not```.\n",
"\n",
"The ```and``` operator evaluates to True if **both** of its arguments are\n",
"True. The ```or``` operator evaluates to True if **either or both**\n",
"of its arguments are true. The ```not``` operator takes only one\n",
"argument and reverses it: ```not``` turns True into False and False\n",
"into True."
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "5ca9f0ff-f7e1-4632-bd02-a133817ab79e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Doctor of Medical Dentistry (DMD)\n"
]
}
],
"source": [
"medical_license = True\n",
"dental_license = True\n",
"\n",
"if medical_license and dental_license:\n",
" print(\"Doctor of Medical Dentistry (DMD)\")\n",
"elif dental_license and not medical_license:\n",
" print(\"Plain old dentist.\")\n",
"elif not dental_license and medical_license:\n",
" print(\"Garden-variety doctor.\")\n",
"else:\n",
" print(\"No license at all. Run. Quickly.\")"
]
},
{
"cell_type": "markdown",
"id": "6a0b39c4-2ad1-4d22-b5ca-7d5e66466bad",
"metadata": {},
"source": [
"## Coming Up Next: Loops\n",
"\n",
"At this point, we've seen the most basic way to alter the *flow of control* in Python: the `if` statement. We can write Python code to solve non-trivial problems now, but there are still some things we need in order to use Python as a truly general-purpose language. In the next notebook we're going to make our code do something over and over."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4772f171-c5de-4c62-b59d-92572bb88232",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}