Title: Writing Algorithms using Python Constructs Date: 2024-12-19 Summary: Learning Notes: [https://docs.google.com/document/d/](https://docs.google.com/document/d/1dPTWdNlQA2H2cDqxlVRRGb6ITOJRVPiCiR-kBSI5Uac/edit?usp=sharing) Status: published # Arithmetic Operations ## Divide and "take the floor" with the `//` operator "Taking the floor" of a number and truncating it are the same for positive numbers: ``` In: 5/2 Out: 2.5 In: 5//2 Out: 2 In: math.trunc(5/2) Out: 2 ``` The floor rounds it toward negative infinity and truncating rounds toward zero. When the numbers are negative, taking the floor and truncating aren't the same: ``` In: -5/2 Out: -2.5 In: -5//2 Out: -3 In: math.trunc(-5/2) Out: -2 ``` When you need to round toward positive infinity, you can "take the ceiling": ``` In: 5/2 Out: 2.5 In: math.ceil(5/2) Out: 3 In: math.ceil(-5/2) Out: -2 ``` # Control Flow ## Check conditions in order and select the first true one with `if … elif … elif …` When the conditions are mutually exclusive and exhaustive, it's easy to reason about: ```{python} In: output = [] for x in range(1, 5): if x % 3 == 0: output.append("Fizz") elif x % 3 != 0: output.append("not Fizz") Out: ['not Fizz', 'not Fizz', 'Fizz', 'not Fizz'] ``` When the conditions aren't mutually exclusive, the order of the conditions matters: ```{python} In: output = [] for x in range(12, 16): if x % 3 == 0: output.append("Fizz") elif x % 5 == 0: output.append("Buzz") else: output.append(str(x)) Out: ['Fizz', '13', '14', 'Fizz'] ``` ```{python} In: output = [] for x in range(12, 16): if x % 5 == 0: output.append("Buzz") elif x % 3 == 0: output.append("Fizz") else: output.append(str(x)) Out: ['Fizz', '13', '14', 'Buzz'] ``` When the conditions are reordered, the output for `x=15` changes from `Fizz` to `Buzz`. Note the use of the `else` clause to make the `if … elif … elif …` construct exhaustive. To make overlapping conditions easier to reason about, we can explicitly enumerate them using logical operators: ```{python} In: output = [] for x in range(12, 16): if (x % 3 == 0) and (x % 5 == 0): output.append("FizzBuzz") elif (x % 3 == 0) and (x % 5 != 0): output.append("Fizz") elif (x % 5 == 0) and (x % 3 != 0): output.append("Buzz") else: output.append(str(x)) Out: ['Fizz', '13', '14', 'FizzBuzz'] ``` If the overlapping conditions have a natural ordering, we can order them from most to least specific: ```{python} In: x = 10 if x > 20: print("x is greater than 20") elif x > 10: print("x is greater than 10") elif x > 0: print("x is positive") else: print("x is non-positive") Out: x is positive ``` # Data Structures ## Allocate data to dynamic arrays with `list` The Python `list` is a dynamic array that can grow as we need it to: ``` In: cubes = [1, 8, 27, 65, 125] cubes[len(cubes):] = [6 ** 3] cubes[len(cubes):] = [7 ** 3] Out: [1, 8, 27, 64, 125, 216, 343] ``` Note that "indexing" and "slicing" handle out of range indices differently The syntax for slicing is `list[start:stop:step]` where start is inclusive and stop is exclusive If omitted, start defaults to 0 and stop defaults to to the end of the list ``` In: cubes = [1, 8, 27, 64, 125, 216, 343] In: cubes[len(cubes)] Out: IndexError: list index out of range In: cubes[len(cubes):] Out: [] ``` Both "indexing" and "slicing" of Python lists can handle negative indices -1 refers to the last element and negatives indices count from the end of the list ``` In: cubes = [1, 8, 27, 64, 125, 216, 343] In: cubes[-1] Out: 343 In: cubes[-len(cubes)] Out: 1 In: cubes[-1:-len(cubes):-1] Out: [343, 216, 125, 65, 27, 8] ``` We can replace and remove values from a Python `list` using slicing: ``` In: letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] In: letters[2:5] = ['C', 'D', 'E'] Out: ['a', 'b', 'C', 'D', 'E', 'f', 'g'] In: letters[2:5] = [] Out: ['a', 'b', 'f', 'g'] ``` An alternative to indexing and slicing is Python list methods: Note that list.insert(i,x) inserts x before index i ``` In: cubes = [1, 8, 27, 65, 125] In: cubes.append(6**3) Out: [1, 8, 27, 65, 125, 216] In: cubes.insert(len(cubes), 7**3) Out: [1, 8, 27, 65, 125, 216, 343] In: cubes.insert(0,0**3) Out: [0, 1, 8, 27, 65, 125, 216, 343] In: cubes.pop() Out: [0, 1, 8, 27, 65, 125, 216] In: cubes.pop(0) Out: [1, 8, 27, 65, 125, 216] ```