|
409 | 409 | There can always be inputs that cause endless oscillations between values or the appearance of \pythonilIdx{nan} values (see \cref{sec:float:special}).%
|
410 | 410 | }%
|
411 | 411 | %
|
412 |
| -The only reason why we did it here is that it looks nice and is easy to read as a functional and yet brief example for a \pythonilIdx{while} loop. |
| 412 | +The only reason why we did it here is that it looks nice and is easy to read as a functional and yet brief example for a \pythonilIdx{while} loop.\footnote{% |
| 413 | +To enforce that no oscillation between two neighboring \pythonil{float} values could occur, one could use the condition \pythonil{abs(old_guess - guess) / max(old_guess, guess) >= 1e-15} instead. % |
| 414 | +This makes the loop stop when the relative difference between \pythonil{guess} and \pythonil{old_guess} becomes less than~$10^-{15}$.% |
| 415 | +}% |
413 | 416 | Anyway, the loop condition necessitates us to store some value different from~\pythonil{1.0} in \pythonil{old_guess} initially (and we picked~\pythonil{0.0}).
|
414 | 417 | In the loop, first the current guess becomes the old guess via \pythonil{old_guess = guess}.
|
415 | 418 | Then we update the guess as specified in \cref{eq:heronGuessUpdate}, by setting \pythonil{guess = 0.5 * (guess + number / guess)}.
|
|
422 | 425 | \FloatBarrier%
|
423 | 426 | \endhsection%
|
424 | 427 | %
|
| 428 | +% |
| 429 | +\hsection{The \texttt{else} Statement at the Bottom of Loops}% |
| 430 | +% |
| 431 | +Now, you have learned before that we can leave a loop body immediately by calling the~\pythonilIdx{break} statement. |
| 432 | +Let's say that we want to perform a certain action \emph{after} the loop if the loop has completed normally, i.e., if \pythonilIdx{break} was not invoked. |
| 433 | +We can do this by declaring a Boolean variable denoting whether the loop has completed normally before the loop and initializing it with \pythonil{True}. |
| 434 | +If we invoke \pythonilIdx{break}, then we would first set this variable to \pythonil{False}. |
| 435 | +After the loop, we could place an \pythonil{if} to check if the variable is still \pythonil{True} and then execute the action. |
| 436 | +This is totally fine, but \python\ offers us a much less verbose method: |
| 437 | +Using the \pythonilIdx{else} statement at the bottom of the loop, which works for both \pythonilIdx{for} and \pythonilIdx{while} loops:% |
| 438 | +% |
| 439 | +\begin{pythonSyntax} |
| 440 | +for loopVariable in sequence: |
| 441 | + loop body statement 1 |
| 442 | + loop body statement 2 |
| 443 | + # ... |
| 444 | +else: |
| 445 | + code executed if break not invoked 1 |
| 446 | + # ... |
| 447 | +normal statement 1 |
| 448 | +normal statement 2 |
| 449 | +# ... |
| 450 | +\end{pythonSyntax} |
| 451 | +% |
| 452 | +\begin{pythonSyntax} |
| 453 | +while booleanExpression: |
| 454 | + loop body statement 1 |
| 455 | + loop body statement 2 |
| 456 | + # ... |
| 457 | +else: |
| 458 | + code executed if break not invoked 1 |
| 459 | + # ... |
| 460 | +normal statement 1 |
| 461 | +normal statement 2 |
| 462 | +# ... |
| 463 | +\end{pythonSyntax} |
| 464 | +% |
| 465 | +\gitPythonAndOutput{\programmingWithPythonCodeRepo}{04_loops}{while_loop_search.py}{--args format}{loops:while_loop_search}{% |
| 466 | +We implement binary search~\cite{K1998SAS,H2024POICBS,B1999PP} using a \pythonilIdx{while} loop with a \pythonilIdx{break} statement.}% |
| 467 | +% |
| 468 | +We now use this construct to implement a binary search~\cite{K1998SAS,H2024POICBS,B1999PP}. |
| 469 | +Binary search works is an algorithm that finds the index of an element in a \emph{sorted} sequence \pythonil{data} of values. |
| 470 | +The core concept of binary search is that we consider a segment~$S$ of the list in which the element~$E$ we search may be contained. |
| 471 | +In each step of the algorithm, we want to reduce the size of this segment by ruling out the \emph{half} in which~$E$ cannot be. |
| 472 | +We do this looking at the element~$M$ right in the middle. |
| 473 | +Now, the whole sequence~\pythonil{data} and, hence, also the segment~$S$, are sorted. |
| 474 | +If~$M$ is bigger than~$E$, then $E$ must be in the lower half, i.e., in the sub-segment from the start of~$S$ to right before~$M$. |
| 475 | +If~$M$ is smaller than~$E$, then $E$ must be in the upper half, i.e., in the sub-segment starting right after~$M$ and reaching until the end of~$S$. |
| 476 | +Otherwise, we must have found the element. |
| 477 | +This means in one step we have effectively halved the size of~$S$. |
| 478 | +If~$n=\pythonil{len(data)}$, then we can do this at most $\log_2 n$~times and the time complexity of binary search is in~\bigOb{\log n}~\cite{K1998SAS,H2024POICBS,B1999PP}.% |
| 479 | +% |
| 480 | +\begin{sloppypar}% |
| 481 | +In~\cref{lst:loops:while_loop_search}, we want to find the indices of some characters in the alphabetically sorted string~\pythonil{data = "abdfjlmoqsuvwyz"}. |
| 482 | +Of course, there is the \pythonilIdx{find}\pythonIdx{str!find} method that can do this, but we want to take advantage of the fact that the characters in \pythonil{data} are sorted. |
| 483 | +We search for the six characters~\pythonil{"a"}, \pythonil{"c"}, \pythonil{"o"}, \pythonil{"p"}, \pythonil{"w"}, and~\pythonil{"z"}. |
| 484 | +Four of them are in \pythonil{data}, but \pythonil{"c"} and \pythonil{"p"} are not. |
| 485 | +To search them anyway, we let a variable \pythonil{search} iterate over the list \pythonil{["a", "c", "o", "p", "w", "z"]}.% |
| 486 | +\end{sloppypar}% |
| 487 | +% |
| 488 | +Now in the inner loop, we implement the binary search. |
| 489 | +This search will maintain and update two indices \pythonil{lower} and \pythonil{upper}. |
| 490 | +\pythonil{lower} is the inclusive lower end of the segment in which \pythonil{search} could be contained. |
| 491 | +It is therefore initialized with~\pythonil{0}. |
| 492 | +\pythonil{upper} is the exclusive upper end of the segment in which \pythonil{search} could be contained. |
| 493 | +We initialize it with \pythonil{len(data)}; as it is \emph{exclusive}, it will be 1 bigger than the largest valid index \pythonil{len(data) - 1}. |
| 494 | +Our segment is not empty, i.e., contains at least one element, as long as \pythonil{lower < upper}. |
| 495 | +This is therefore the loop condition of the inner loop. |
| 496 | + |
| 497 | +Inside the binary search loop, we first compute the mid index as \pythonil{mid = (lower + upper) // 2}\footnote{% |
| 498 | +Interestingly, this works only because \python~3 has integers of infinite range (see \cref{sec:int}). % |
| 499 | +In programming languages like \pgls{C} or \pgls{Java} where integer types have limited ranges, we need to do \pythonil{mid = lower + (upper - lower) // 2}~\cite{H2024POICBS}.% |
| 500 | +}. |
| 501 | +We obtain the value \pythonil{mid_str} as the single character at that index via \pythonil{mid_str = data[mid]}. |
| 502 | + |
| 503 | +We know that if \pythonil{mid_str < search}, then our character \pythonil{search} cannot be located at any index in the range~\intRange{0}{\pythonil{mid}}. |
| 504 | +So in this case, we can update the \emph{inclusive} index \pythonil{lower} to become \pythonil{mid + 1}. |
| 505 | +Otherwise, if \pythonil{mid_str > search}, then we know that \pythonil{search} could not possibly located anywhere in the range~\intRange{\pythonil{mid}}{\pythonil{len(data) - 1}}. |
| 506 | +We thus would set the \emph{exclusive} index \pythonil{upper} to \pythonil{mid}, which excludes all items starting at index \pythonil{mid} from further consideration.% |
| 507 | +% |
| 508 | +\begin{sloppypar}% |
| 509 | +Now if neither \pythonil{mid_str < search} nor \pythonil{mid_str > search} were \pythonil{True}, it must be that \pythonil{mid_str == search}. |
| 510 | +This means that we found the location of \pythonil{search} -- it is at the index~\pythonil{mid}. |
| 511 | +Therefore, we print this result to the output. |
| 512 | +(Notice that the \pythonilIdx{!r} format specifiers in the \pgls{fstring} we use add the nice single quotes around \pythonil{search} and \pythonil{data}.) |
| 513 | +After printing the information, we exit the \pythonilIdx{while} loop using the \pythonilIdx{break} statement.% |
| 514 | +\end{sloppypar}% |
| 515 | +% |
| 516 | +Now, there is the possibility that we cannot find \pythonil{search} in \pythonil{data} because it is simple in there. |
| 517 | +In this case, we will never print the output and also not leave the loop with \pythonilIdx{break}. |
| 518 | +In each iteration that does not end with \pythonilIdx{break}, we will either increase \pythonil{lower} or decrease \pythonil{upper}. |
| 519 | +Thus, eventually \pythonil{lower < upper} will become \pythonil{False}. |
| 520 | +This is when the \pythonilIdx{else} statement at the bottom of the loop is executed. |
| 521 | +Then and only then we print that we did not find the string.% |
| 522 | +\endhsection% |
| 523 | +% |
| 524 | +\hsection{Summary}% |
| 525 | +With this, we depart from the subject of loops. |
| 526 | +We have learned two ways to execute code iteratively: |
| 527 | +The \pythonilIdx{for} loop iterates of sequences of objects, which can either be \pythonilsIdx{range} of numbers or arbitrary collections. |
| 528 | +The \pythonilIdx{while} loop permits us to specify an arbitrary Boolean expression as loop condition. |
| 529 | +In the bodies of both loops, we can jump to the next iteration at any time using the \pythonilIdx{continue} statement or we can exit the loops entirely using the \pythonil{break} statement. |
| 530 | +Finally, placing an \pythonilIdx{else} statement at the bottom of the loop allows us to execute some code when the loop completes regularly, i.e., not via~\pythonilIdx{break}. |
| 531 | + |
| 532 | +We now have some nice tools in our hands. |
| 533 | +We can create code that branches and conditionally performs actions via \pythonil{if}-\pythonil{else}. |
| 534 | +And we can repeatedly perform actions via \pythonil{for} and \pythonil{while}. |
| 535 | +Our examples also have become more elaborate and interesting. |
| 536 | +We can now approximate~\numberPi\ with arbitrarily many steps of the LIU Hui's approach. |
| 537 | +We can implement Heron's Method to compute the square root of a number and we perform binary search over arbitarily large (sorted) data. |
| 538 | +We can do quite a lot! |
| 539 | + |
| 540 | +What we cannot yet do is to have a block of code that we want to re-use in \emph{different} places.% |
| 541 | +\endhsection% |
| 542 | +% |
| 543 | +\FloatBarrier% |
425 | 544 | \endhsection%
|
426 | 545 | %
|
0 commit comments