307 lines
34 KiB
HTML
307 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="generator" content="pandoc" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||
<title>3.4 Conditional Execution</title>
|
||
<style>
|
||
code{white-space: pre-wrap;}
|
||
span.smallcaps{font-variant: small-caps;}
|
||
span.underline{text-decoration: underline;}
|
||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||
ul.task-list{list-style: none;}
|
||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||
div.sourceCode { margin: 1em 0; }
|
||
pre.sourceCode { margin: 0; }
|
||
@media screen {
|
||
div.sourceCode { overflow: auto; }
|
||
}
|
||
@media print {
|
||
pre > code.sourceCode { white-space: pre-wrap; }
|
||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||
}
|
||
pre.numberSource code
|
||
{ counter-reset: source-line 0; }
|
||
pre.numberSource code > span
|
||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||
pre.numberSource code > span > a:first-child::before
|
||
{ content: counter(source-line);
|
||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||
border: none; display: inline-block;
|
||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||
-khtml-user-select: none; -moz-user-select: none;
|
||
-ms-user-select: none; user-select: none;
|
||
padding: 0 4px; width: 4em;
|
||
color: #aaaaaa;
|
||
}
|
||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||
div.sourceCode
|
||
{ }
|
||
@media screen {
|
||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||
}
|
||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||
code span.at { color: #7d9029; } /* Attribute */
|
||
code span.bn { color: #40a070; } /* BaseN */
|
||
code span.bu { } /* BuiltIn */
|
||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||
code span.ch { color: #4070a0; } /* Char */
|
||
code span.cn { color: #880000; } /* Constant */
|
||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||
code span.dt { color: #902000; } /* DataType */
|
||
code span.dv { color: #40a070; } /* DecVal */
|
||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||
code span.ex { } /* Extension */
|
||
code span.fl { color: #40a070; } /* Float */
|
||
code span.fu { color: #06287e; } /* Function */
|
||
code span.im { } /* Import */
|
||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||
code span.op { color: #666666; } /* Operator */
|
||
code span.ot { color: #007020; } /* Other */
|
||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||
code span.ss { color: #bb6688; } /* SpecialString */
|
||
code span.st { color: #4070a0; } /* String */
|
||
code span.va { color: #19177c; } /* Variable */
|
||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||
</style>
|
||
<link rel="stylesheet" href="../tufte.css" />
|
||
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" type="text/javascript"></script>
|
||
<!--[if lt IE 9]>
|
||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||
<![endif]-->
|
||
</head>
|
||
<body>
|
||
<div style="display:none">
|
||
\(
|
||
\newcommand{\NOT}{\neg}
|
||
\newcommand{\AND}{\wedge}
|
||
\newcommand{\OR}{\vee}
|
||
\newcommand{\XOR}{\oplus}
|
||
\newcommand{\IMP}{\Rightarrow}
|
||
\newcommand{\IFF}{\Leftrightarrow}
|
||
\newcommand{\TRUE}{\text{True}\xspace}
|
||
\newcommand{\FALSE}{\text{False}\xspace}
|
||
\newcommand{\IN}{\,{\in}\,}
|
||
\newcommand{\NOTIN}{\,{\notin}\,}
|
||
\newcommand{\TO}{\rightarrow}
|
||
\newcommand{\DIV}{\mid}
|
||
\newcommand{\NDIV}{\nmid}
|
||
\newcommand{\MOD}[1]{\pmod{#1}}
|
||
\newcommand{\MODS}[1]{\ (\text{mod}\ #1)}
|
||
\newcommand{\N}{\mathbb N}
|
||
\newcommand{\Z}{\mathbb Z}
|
||
\newcommand{\Q}{\mathbb Q}
|
||
\newcommand{\R}{\mathbb R}
|
||
\newcommand{\C}{\mathbb C}
|
||
\newcommand{\cA}{\mathcal A}
|
||
\newcommand{\cB}{\mathcal B}
|
||
\newcommand{\cC}{\mathcal C}
|
||
\newcommand{\cD}{\mathcal D}
|
||
\newcommand{\cE}{\mathcal E}
|
||
\newcommand{\cF}{\mathcal F}
|
||
\newcommand{\cG}{\mathcal G}
|
||
\newcommand{\cH}{\mathcal H}
|
||
\newcommand{\cI}{\mathcal I}
|
||
\newcommand{\cJ}{\mathcal J}
|
||
\newcommand{\cL}{\mathcal L}
|
||
\newcommand{\cK}{\mathcal K}
|
||
\newcommand{\cN}{\mathcal N}
|
||
\newcommand{\cO}{\mathcal O}
|
||
\newcommand{\cP}{\mathcal P}
|
||
\newcommand{\cQ}{\mathcal Q}
|
||
\newcommand{\cS}{\mathcal S}
|
||
\newcommand{\cT}{\mathcal T}
|
||
\newcommand{\cV}{\mathcal V}
|
||
\newcommand{\cW}{\mathcal W}
|
||
\newcommand{\cZ}{\mathcal Z}
|
||
\newcommand{\emp}{\emptyset}
|
||
\newcommand{\bs}{\backslash}
|
||
\newcommand{\floor}[1]{\left \lfloor #1 \right \rfloor}
|
||
\newcommand{\ceil}[1]{\left \lceil #1 \right \rceil}
|
||
\newcommand{\abs}[1]{\left | #1 \right |}
|
||
\newcommand{\xspace}{}
|
||
\newcommand{\proofheader}[1]{\underline{\textbf{#1}}}
|
||
\)
|
||
</div>
|
||
<header id="title-block-header">
|
||
<h1 class="title">3.4 Conditional Execution</h1>
|
||
</header>
|
||
<section>
|
||
<p>So far, all of the function bodies we’ve written have consisted of a sequence of statements that always execute one after the other.<label for="sn-0" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-0" class="margin-toggle"/><span class="sidenote"> This kind of code block is sometimes called a “straight line program”, since the statements form a linear path from one to the next.</span> But sometimes we want to execute a statement or block of statements only some of the time, based on some condition.</p>
|
||
<p>This is similar to the implication operator we saw when discussing propositional logic. The implication <span class="math inline">\(p \Rightarrow q\)</span> states that whenever <span class="math inline">\(p\)</span> is True, <span class="math inline">\(q\)</span> must also be True. In Python, what we would like to express is something of the form “Whenever <span class="math inline">\(p\)</span> is True, then the block of code <code>block1</code> must be executed”. To do so, we’ll introduce a new type of Python statement that play a role analogous to <span class="math inline">\(\Rightarrow\)</span> in propositional logic.</p>
|
||
<h2 id="the-if-statement">The if statement</h2>
|
||
<p>Python uses the <strong>if statement</strong> to express conditional execution of code. An if statement is a <strong>compound statement</strong>, meaning it contains other statements within it.<label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle"/><span class="sidenote"> Analogously, a expression like <code>3 + 4</code> is a <em>compound expression</em>, since it consists of smaller expressions (<code>3</code> and <code>4</code>).</span> Here is our first syntax for an if statement:</p>
|
||
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1"></a><span class="cf">if</span> <span class="op"><</span>condition<span class="op">></span>:</span>
|
||
<span id="cb1-2"><a href="#cb1-2"></a> <span class="op"><</span>statement<span class="op">></span></span>
|
||
<span id="cb1-3"><a href="#cb1-3"></a> ...</span>
|
||
<span id="cb1-4"><a href="#cb1-4"></a><span class="cf">else</span>:</span>
|
||
<span id="cb1-5"><a href="#cb1-5"></a> <span class="op"><</span>statement<span class="op">></span></span>
|
||
<span id="cb1-6"><a href="#cb1-6"></a> ...</span></code></pre></div>
|
||
<p>The if statement uses two keywords, <code>if</code> and <code>else</code>.<label for="sn-2" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-2" class="margin-toggle"/><span class="sidenote"> Careful: we saw the <code>if</code> keyword used earlier to express conditions in comprehensions. The use of <code>if</code> here is logically similar, but quite different in how Python interprets it.</span> The <code><condition></code> following <code>if</code> must be an expression that evaluates to a boolean, called the <strong>if condition</strong>. This expression plays a role analogous to the hypothesis of an implication.</p>
|
||
<p>The statements on the lines after the <code>if</code> and <code>else</code> are indented to indicate that they are part of the <code>if</code> statement, similar to how a function docstring and body are indented relative to the function header. We call the statements under the <code>if</code> the <strong>if branch</strong> and the statements under the <code>else</code> the <strong>else branch</strong>.</p>
|
||
<p>When an <code>if</code> statement is executed, the following happens:</p>
|
||
<ol type="1">
|
||
<li>First, the if condition is evaluated, producing a boolean value.</li>
|
||
<li>If the condition evaluates to <code>True</code>, then the statements in the if branch are executed. If the condition evaluates to <code>False</code>, then the statements in the else branch are executed instead.</li>
|
||
</ol>
|
||
<p>Let us consider an example. Suppose Toronto Pearson Airport (YYZ) has hired us to develop some software. The first feature they want is to show their clients if a flight is on time or delayed. The airport will provide us with both the time a flight is scheduled to depart and an estimated departure time based on the plane’s current GPS location. Our task is to report a status (as a string) to display a string. Here is the function header and docstring:</p>
|
||
<div class="sourceCode" id="cb2"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1"></a><span class="kw">def</span> get_status(scheduled: <span class="bu">int</span>, estimated: <span class="bu">int</span>) <span class="op">-></span> <span class="bu">str</span>:</span>
|
||
<span id="cb2-2"><a href="#cb2-2"></a> <span class="co">"""Return the flight status for the given scheduled and estimated departure times.</span></span>
|
||
<span id="cb2-3"><a href="#cb2-3"></a></span>
|
||
<span id="cb2-4"><a href="#cb2-4"></a><span class="co"> The times are given as integers between 0 and 23 inclusive, representing</span></span>
|
||
<span id="cb2-5"><a href="#cb2-5"></a><span class="co"> the hour of the day.</span></span>
|
||
<span id="cb2-6"><a href="#cb2-6"></a></span>
|
||
<span id="cb2-7"><a href="#cb2-7"></a><span class="co"> The status is either 'On time' or 'Delayed'.</span></span>
|
||
<span id="cb2-8"><a href="#cb2-8"></a></span>
|
||
<span id="cb2-9"><a href="#cb2-9"></a><span class="co"> >>> get_status(10, 10)</span></span>
|
||
<span id="cb2-10"><a href="#cb2-10"></a><span class="co"> 'On time'</span></span>
|
||
<span id="cb2-11"><a href="#cb2-11"></a><span class="co"> >>> get_status(10, 12)</span></span>
|
||
<span id="cb2-12"><a href="#cb2-12"></a><span class="co"> 'Delayed'</span></span>
|
||
<span id="cb2-13"><a href="#cb2-13"></a><span class="co"> """</span></span></code></pre></div>
|
||
<p>Now, if we only needed to calculate a <code>bool</code> for whether the flight is delayed, this function would be very straightforward: simply return <code>estimated <= scheduled</code>, i.e., whether the estimated departure time is before or at the scheduled departure time. Boolean expressions like this are often useful first steps in implementing functions to determine different “cases” of inputs, but they aren’t the only step.</p>
|
||
<p>Instead, we use if statements to execute different code based on these cases. Here’s our implementation of <code>get_status</code>:</p>
|
||
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1"></a><span class="kw">def</span> get_status(scheduled: <span class="bu">int</span>, estimated: <span class="bu">int</span>) <span class="op">-></span> <span class="bu">str</span>:</span>
|
||
<span id="cb3-2"><a href="#cb3-2"></a> <span class="co">"""..."""</span></span>
|
||
<span id="cb3-3"><a href="#cb3-3"></a> <span class="cf">if</span> estimated <span class="op"><=</span> scheduled:</span>
|
||
<span id="cb3-4"><a href="#cb3-4"></a> <span class="cf">return</span> <span class="st">'On time'</span></span>
|
||
<span id="cb3-5"><a href="#cb3-5"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb3-6"><a href="#cb3-6"></a> <span class="cf">return</span> <span class="st">'Delayed'</span></span></code></pre></div>
|
||
<p>Our if statement uses the boolean expression we identified earlier (<code>estimated <= scheduled</code>) to trigger different <code>return</code> statements to return the correct string.</p>
|
||
<h3 id="a-simple-control-flow-diagram">A simple control flow diagram</h3>
|
||
<p>One useful tool for understanding if statements is drawing <em>control flow diagrams</em> to visualize the order in which statements execute. For example, here is a simple diagram for our <code>get_status</code> function above:</p>
|
||
<p><img src="images/get-status.png" alt="get status control flow diagram" /><br />
|
||
</p>
|
||
<p>An <code>if</code> statement introduces a “fork in path” of a function’s control flow, and this is why we use the term <em>branch</em> for each of the <code>if</code> and <code>else</code> blocks of code.</p>
|
||
<h2 id="code-with-more-than-two-cases">Code with more than two cases</h2>
|
||
<p>Now suppose Toronto Pearson Airport has changed the requirements of our feature. They’ve noticed that whenever a flight is delayed by more than four hours, the airline cancels the flight. They would like our <code>get_status</code> function to accommodate this change, so that the set of possible outputs is now <code>{'On time', 'Delayed', 'Cancelled'}</code>.</p>
|
||
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1"></a><span class="kw">def</span> get_status_v2(scheduled: <span class="bu">int</span>, estimated: <span class="bu">int</span>) <span class="op">-></span> <span class="bu">str</span>:</span>
|
||
<span id="cb4-2"><a href="#cb4-2"></a> <span class="co">"""Return the flight status for the given scheduled and estimated departure times.</span></span>
|
||
<span id="cb4-3"><a href="#cb4-3"></a></span>
|
||
<span id="cb4-4"><a href="#cb4-4"></a><span class="co"> The times are given as integers between 0 and 23 inclusive, representing</span></span>
|
||
<span id="cb4-5"><a href="#cb4-5"></a><span class="co"> the hour of the day.</span></span>
|
||
<span id="cb4-6"><a href="#cb4-6"></a></span>
|
||
<span id="cb4-7"><a href="#cb4-7"></a><span class="co"> The status is 'On time', 'Delayed', or 'Cancelled'.</span></span>
|
||
<span id="cb4-8"><a href="#cb4-8"></a></span>
|
||
<span id="cb4-9"><a href="#cb4-9"></a><span class="co"> >>> get_status_v2(10, 10)</span></span>
|
||
<span id="cb4-10"><a href="#cb4-10"></a><span class="co"> 'On time'</span></span>
|
||
<span id="cb4-11"><a href="#cb4-11"></a><span class="co"> >>> get_status_v2(10, 12)</span></span>
|
||
<span id="cb4-12"><a href="#cb4-12"></a><span class="co"> 'Delayed'</span></span>
|
||
<span id="cb4-13"><a href="#cb4-13"></a><span class="co"> >>> get_status_v2(10, 15)</span></span>
|
||
<span id="cb4-14"><a href="#cb4-14"></a><span class="co"> 'Cancelled'</span></span>
|
||
<span id="cb4-15"><a href="#cb4-15"></a><span class="co"> """</span></span></code></pre></div>
|
||
<p>Let’s consider what’s changed between this version and our previous one. If the estimated time is before the scheduled time, nothing’s changed, and <code>'On time'</code> should still be returned. But when the estimated time is after the schedule time, we now need to distinguish between two separate subcases, based on the difference in time. We can express these subcases using nested if statements, i.e., one if statement contained in a branch of another:</p>
|
||
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1"></a><span class="kw">def</span> get_status_v2(scheduled: <span class="bu">int</span>, estimated: <span class="bu">int</span>) <span class="op">-></span> <span class="bu">str</span>:</span>
|
||
<span id="cb5-2"><a href="#cb5-2"></a> <span class="co">"""..."""</span></span>
|
||
<span id="cb5-3"><a href="#cb5-3"></a> <span class="cf">if</span> estimated <span class="op"><=</span> scheduled:</span>
|
||
<span id="cb5-4"><a href="#cb5-4"></a> <span class="cf">return</span> <span class="st">'On time'</span></span>
|
||
<span id="cb5-5"><a href="#cb5-5"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb5-6"><a href="#cb5-6"></a> <span class="cf">if</span> estimated <span class="op">-</span> scheduled <span class="op"><=</span> <span class="dv">4</span>:</span>
|
||
<span id="cb5-7"><a href="#cb5-7"></a> <span class="cf">return</span> <span class="st">'Delayed'</span></span>
|
||
<span id="cb5-8"><a href="#cb5-8"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb5-9"><a href="#cb5-9"></a> <span class="cf">return</span> <span class="st">'Cancelled'</span></span></code></pre></div>
|
||
<p>This function body is correct, but just like with expressions, excessive nesting of statements can make code difficult to read and understand. So instead of using a nested if statement, we’ll introduce a new form of if statement that makes use of the <code>elif</code> keyword, which is short for “else if”.</p>
|
||
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1"></a><span class="cf">if</span> <span class="op"><</span>condition1<span class="op">></span>:</span>
|
||
<span id="cb6-2"><a href="#cb6-2"></a> <span class="op"><</span>statement<span class="op">></span></span>
|
||
<span id="cb6-3"><a href="#cb6-3"></a> ...</span>
|
||
<span id="cb6-4"><a href="#cb6-4"></a><span class="cf">elif</span> <span class="op"><</span>condition2<span class="op">></span>:</span>
|
||
<span id="cb6-5"><a href="#cb6-5"></a> <span class="op"><</span>statement<span class="op">></span></span>
|
||
<span id="cb6-6"><a href="#cb6-6"></a> ...</span>
|
||
<span id="cb6-7"><a href="#cb6-7"></a>... <span class="co"># [any number of elif conditions and branches]</span></span>
|
||
<span id="cb6-8"><a href="#cb6-8"></a><span class="cf">else</span>:</span>
|
||
<span id="cb6-9"><a href="#cb6-9"></a> <span class="op"><</span>statement<span class="op">></span></span>
|
||
<span id="cb6-10"><a href="#cb6-10"></a> ...</span></code></pre></div>
|
||
<p>When this form of if statement is executed, the following happens.</p>
|
||
<ol type="1">
|
||
<li>First, the if condition (<code><condition1></code>) is evaluated, producing a boolean value.</li>
|
||
<li>If the condition evaluates to <code>True</code>, then the statements in the if branch are executed. If the condition evaluates to <code>False</code>, then next elif condition is evaluated, producing a boolean.</li>
|
||
<li>If that condition evaluates to <code>True</code>, then the statements in that elif branch are executed. If that condition evaluates to <code>False</code>, then the next elif condition is evaluated. This step repeats until either one of the elif conditions evaluate to <code>True</code>, or all of the elif conditions have evaluated to <code>False</code>.</li>
|
||
<li>If neither the if condition nor any of the elif conditions evaluate to <code>True</code>, then the else branch executes.</li>
|
||
</ol>
|
||
<p>Here is how we can use <code>elif</code> to rewrite <code>get_status</code> without nested if statements.</p>
|
||
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1"></a><span class="kw">def</span> get_status_v3(scheduled: <span class="bu">int</span>, estimated: <span class="bu">int</span>) <span class="op">-></span> <span class="bu">str</span>:</span>
|
||
<span id="cb7-2"><a href="#cb7-2"></a> <span class="co">"""Return the flight status for the given scheduled and estimated departure times.</span></span>
|
||
<span id="cb7-3"><a href="#cb7-3"></a></span>
|
||
<span id="cb7-4"><a href="#cb7-4"></a><span class="co"> The times are given as integers between 0 and 23 inclusive, representing</span></span>
|
||
<span id="cb7-5"><a href="#cb7-5"></a><span class="co"> the hour of the day.</span></span>
|
||
<span id="cb7-6"><a href="#cb7-6"></a></span>
|
||
<span id="cb7-7"><a href="#cb7-7"></a><span class="co"> The status is 'On time', 'Delayed', or 'Cancelled'.</span></span>
|
||
<span id="cb7-8"><a href="#cb7-8"></a></span>
|
||
<span id="cb7-9"><a href="#cb7-9"></a><span class="co"> >>> get_status_v3(10, 10)</span></span>
|
||
<span id="cb7-10"><a href="#cb7-10"></a><span class="co"> 'On time'</span></span>
|
||
<span id="cb7-11"><a href="#cb7-11"></a><span class="co"> >>> get_status_v3(10, 12)</span></span>
|
||
<span id="cb7-12"><a href="#cb7-12"></a><span class="co"> 'Delayed'</span></span>
|
||
<span id="cb7-13"><a href="#cb7-13"></a><span class="co"> >>> get_status_v3(10, 15)</span></span>
|
||
<span id="cb7-14"><a href="#cb7-14"></a><span class="co"> 'Cancelled'</span></span>
|
||
<span id="cb7-15"><a href="#cb7-15"></a><span class="co"> """</span></span>
|
||
<span id="cb7-16"><a href="#cb7-16"></a> <span class="cf">if</span> estimated <span class="op"><=</span> scheduled:</span>
|
||
<span id="cb7-17"><a href="#cb7-17"></a> <span class="cf">return</span> <span class="st">'On time'</span></span>
|
||
<span id="cb7-18"><a href="#cb7-18"></a> <span class="cf">elif</span> estimated <span class="op">-</span> scheduled <span class="op"><=</span> <span class="dv">4</span>:</span>
|
||
<span id="cb7-19"><a href="#cb7-19"></a> <span class="cf">return</span> <span class="st">'Delayed'</span></span>
|
||
<span id="cb7-20"><a href="#cb7-20"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb7-21"><a href="#cb7-21"></a> <span class="cf">return</span> <span class="st">'Cancelled'</span></span></code></pre></div>
|
||
<p>This code is logically equivalent to the previous version, but it’s easier to read because there’s no more nesting! Now, it is clear exactly what are the three possible branches of execution for this function.</p>
|
||
<!-- TODO: Add control flow diagram for get_status_v2 and get_status_v3 -->
|
||
<h2 id="testing-all-the-branches">Testing all the branches</h2>
|
||
<p>Adding branching to our control flow makes our functions more complex, and so we need to pay attention to how we test our code. With functions that contain if statements, any one particular input we give can only test one possible execution path, so we need to design our unit tests so that each possible execution path is used at least once. This form of test design is called <strong>white box</strong> testing, because we “see through the box” and therefore can design tests based on the source code itself. In contrast, <strong>black box</strong> testing are tests created without any knowledge of the source code (so no knowledge of the different paths the code can take).</p>
|
||
<p>In our doctests for <code>get_status_v3</code>, we chose three different examples, each corresponding to a different possible case of the if statement. This was pretty straightforward because the code is relatively simple, but we’ll study later example of more complex control flow where it won’t be so simple to design test cases to cover each branch. In fact, the percentage of lines of program code that are executed when a set of tests for that program is called <strong>code coverage</strong>, and is a metric used to assess the quality of tests. While a set of tests may strive for 100% code coverage, this does not always occur as our programs grow in complexity. The concept of code coverage and other metrics used to evaluate tests is something we’ll only touch on in this course, but in future courses you’ll learn about this in more detail and even use some automated tools for calculating these metrics.<label for="sn-3" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-3" class="margin-toggle"/><span class="sidenote"> In particular, even though code coverage is a commonly used metric, it is also criticized for giving a false sense of quality of a test suite. Just because all lines of code are executed at least once does not actually mean that the tests chosen cover all possible cases to consider for a program. We’ll see a simple example of this in the following section.</span></p>
|
||
<h2 id="building-on-our-example">Building on our example</h2>
|
||
<p>Toronto Pearson Airport is beginning to trust us with more data, and are requesting more complex features as a result. They now want us to write a function that determines how many flights are cancelled in a day. The airport will provide us with the data as a dictionary (i.e., <code>dict</code>), where the keys are unique flight numbers and the values for each flight number is a two-element list. The first element is the scheduled time and the second element is the estimated time. More succinctly, the data is a mapping of the form: <code>{ flight_number: [scheduled, estimated] }</code>.</p>
|
||
<p>Unlike earlier, when our function input was only two integers, we are now working with a collection of data. Before we start trying to solve the problem, let’s create some example data in the Python console. Specifically, we’ll create a dictionary with values for three different Air Canada flight numbers.</p>
|
||
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1"></a><span class="op">>>></span> flights <span class="op">=</span> {<span class="st">'AC110'</span>: [<span class="dv">10</span>, <span class="dv">12</span>], <span class="st">'AC321'</span>: [<span class="dv">12</span>, <span class="dv">19</span>], <span class="st">'AC999'</span>: [<span class="dv">1</span>, <span class="dv">1</span>]}</span>
|
||
<span id="cb8-2"><a href="#cb8-2"></a><span class="op">>>></span> flights[<span class="st">'AC110'</span>]</span>
|
||
<span id="cb8-3"><a href="#cb8-3"></a>[<span class="dv">10</span>, <span class="dv">12</span>]</span></code></pre></div>
|
||
<p>We know that we can query the dictionary by providing an existing key. The value associated with a key is a list of integers, and we can index the list to retrieve those integers. Index 0 of the list refers to the flight number’s scheduled time, while index 1 refers to the estimated time. Let us call our <code>get_status_v3</code> function for flight <code>'AC110'</code>:</p>
|
||
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1"></a><span class="op">>>></span> flight_ac110 <span class="op">=</span> flights[<span class="st">'AC110'</span>]</span>
|
||
<span id="cb9-2"><a href="#cb9-2"></a><span class="op">>>></span> get_status_v3(flight_ac110[<span class="dv">0</span>], flight_ac110[<span class="dv">1</span>])</span>
|
||
<span id="cb9-3"><a href="#cb9-3"></a><span class="co">'Delayed'</span></span></code></pre></div>
|
||
<p>We’re making great progress! Instead of specifying the flight number ourselves (i.e., <code>'AC110'</code>), we would instead like to substitute in different flight numbers based on the data we receive from the airport. We can do that using comprehensions. Let’s explore and see what we can get:</p>
|
||
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1"></a><span class="op">>>></span> {k <span class="cf">for</span> k <span class="kw">in</span> flights}</span>
|
||
<span id="cb10-2"><a href="#cb10-2"></a>{<span class="st">'AC999'</span>, <span class="st">'AC110'</span>, <span class="st">'AC321'</span>}</span>
|
||
<span id="cb10-3"><a href="#cb10-3"></a><span class="op">>>></span> {get_status_v3(flights[k][<span class="dv">0</span>], flights[k][<span class="dv">1</span>]) <span class="op">==</span> <span class="st">'Cancelled'</span> <span class="cf">for</span> k <span class="kw">in</span> flights}</span>
|
||
<span id="cb10-4"><a href="#cb10-4"></a>{<span class="va">False</span>, <span class="va">True</span>}</span>
|
||
<span id="cb10-5"><a href="#cb10-5"></a><span class="op">>>></span> [get_status_v3(flights[k][<span class="dv">0</span>], flights[k][<span class="dv">1</span>]) <span class="op">==</span> <span class="st">'Cancelled'</span> <span class="cf">for</span> k <span class="kw">in</span> flights]</span>
|
||
<span id="cb10-6"><a href="#cb10-6"></a>[<span class="va">False</span>, <span class="va">True</span>, <span class="va">False</span>]</span></code></pre></div>
|
||
<p>Our first set comprehension can get us the set of flight numbers, but that doesn’t tell us if the flight was cancelled or not. When we created our second set comprehension we could see that there was at least one flight cancelled. Remember that sets only contain unique elements, and this set consists of all possible boolean values. When we create a list comprehension, we can see that exactly one out of three flights were cancelled (there is one <code>True</code> value). But remember that the airport only wants to know how many flights were cancelled; a single integer value. Currently, we have a list of boolean values.</p>
|
||
<p>Let us now try to combine the first set comprehension with the second, using the filtering we learned in the last section.</p>
|
||
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1"></a><span class="op">>>></span> {k <span class="cf">for</span> k <span class="kw">in</span> flights <span class="cf">if</span> get_status_v3(flights[k][<span class="dv">0</span>], flights[k][<span class="dv">1</span>]) <span class="op">==</span> <span class="st">'Cancelled'</span>}</span>
|
||
<span id="cb11-2"><a href="#cb11-2"></a>{<span class="st">'AC321'</span>}</span>
|
||
<span id="cb11-3"><a href="#cb11-3"></a><span class="op">>>></span> [k <span class="cf">for</span> k <span class="kw">in</span> flights <span class="cf">if</span> get_status_v3(flights[k][<span class="dv">0</span>], flights[k][<span class="dv">1</span>]) <span class="op">==</span> <span class="st">'Cancelled'</span>]</span>
|
||
<span id="cb11-4"><a href="#cb11-4"></a>[<span class="st">'AC321'</span>]</span></code></pre></div>
|
||
<p>Excellent! We now have a set of flight numbers that were cancelled. To convert this into an integer, we can use the built-in <code>len</code> function on the set.<label for="sn-4" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-4" class="margin-toggle"/><span class="sidenote"> Something to think about: does it matter if we use the list or set comprehension here?</span> Let’s see what all this looks like in a function:</p>
|
||
<div class="sourceCode" id="cb12"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-1"></a><span class="kw">def</span> count_cancelled(flights: <span class="bu">dict</span>) <span class="op">-></span> <span class="bu">int</span>:</span>
|
||
<span id="cb12-2"><a href="#cb12-2"></a> <span class="co">"""Return the number of cancelled flights for the given flight data.</span></span>
|
||
<span id="cb12-3"><a href="#cb12-3"></a></span>
|
||
<span id="cb12-4"><a href="#cb12-4"></a><span class="co"> flights is a dictionary where each key is a flight ID,</span></span>
|
||
<span id="cb12-5"><a href="#cb12-5"></a><span class="co"> and whose corresponding value is a list of two numbers, where the first is</span></span>
|
||
<span id="cb12-6"><a href="#cb12-6"></a><span class="co"> the scheduled departure time and the second is the estimated departure time.</span></span>
|
||
<span id="cb12-7"><a href="#cb12-7"></a></span>
|
||
<span id="cb12-8"><a href="#cb12-8"></a><span class="co"> >>> count_cancelled({'AC110': [10, 12], 'AC321': [12, 19], 'AC999': [1, 1]})</span></span>
|
||
<span id="cb12-9"><a href="#cb12-9"></a><span class="co"> 1</span></span>
|
||
<span id="cb12-10"><a href="#cb12-10"></a><span class="co"> """</span></span>
|
||
<span id="cb12-11"><a href="#cb12-11"></a> cancelled_flights <span class="op">=</span> {k <span class="cf">for</span> k <span class="kw">in</span> flights</span>
|
||
<span id="cb12-12"><a href="#cb12-12"></a> <span class="cf">if</span> get_status_v3(flights[k][<span class="dv">0</span>], flights[k][<span class="dv">1</span>]) <span class="op">==</span> <span class="st">'Cancelled'</span>}</span>
|
||
<span id="cb12-13"><a href="#cb12-13"></a> <span class="cf">return</span> <span class="bu">len</span>(cancelled_flights)</span></code></pre></div>
|
||
<p>Let’s review what we learned in this example:</p>
|
||
<ul>
|
||
<li>We can try to remember how we can use what we’ve learned by exploring in the Python console, well before starting to write the function. Here, we refreshed our memory on how we might look up values from dictionaries, index lists, call functions, create comprehensions, and filter collections.</li>
|
||
<li>We can substitute in different values for a function’s input using comprehensions.</li>
|
||
<li>We can reuse functions we’ve already created and tested (like <code>get_status_v3</code>) to help implement other functions.</li>
|
||
</ul>
|
||
</section>
|
||
<footer>
|
||
<a href="https://www.teach.cs.toronto.edu/~csc110y/fall/notes/">CSC110 Course Notes Home</a>
|
||
</footer>
|
||
</body>
|
||
</html>
|