def add_5(num):
return num + 5
add_5(10)15
DSAN 5500: Data Structures, Objects, and Algorithms in Python
Today’s Planned Schedule:
| Start | End | Topic | |
|---|---|---|---|
| Lecture | 6:30pm | 6:45pm | Final Projects → |
| 6:45pm | 7:00pm | HW4 on Jetstream → | |
| 7:00pm | 8:00pm | Beyond Embarrassingly Parallel → | |
| Break! | 8:00pm | 8:10pm | |
| 8:10pm | 9:00pm | Data Mining in Parallel → |
ThingContainer.| Linear | Logarithmic | Constant | ||
|---|---|---|---|---|
LinkedList\(O(n)\) |
\(\rightarrow\) | BST\(O(\log_2(n))\) |
\(\rightarrow\) | HashTable\(O(1 + \varepsilon)\) |
| Linear | Logarithmic | Constant | ||
|---|---|---|---|---|
| Insertion-Sort \(O(n^2)\) |
\(\rightarrow\) | Merge-Sort \(O(n\log_2(n))\) |
\(\rightarrow\) | ??? |
0 \(\leadsto\) def make_zero(): return 0 🤯)map() and reduce() are “meta-functions”: functions that take other functions as inputsdef add_5(num):
return num + 5
add_5(10)15
def apply_twice(fn, arg):
return fn(fn(arg))
apply_twice(add_5, 10)20
lambda):add_5 = lambda num: num + 5
apply_twice(add_5, 10)20
When a program doesn’t work, each function is an interface point where you can check that the data are correct. You can look at the intermediate inputs and outputs to quickly isolate the function that’s responsible for a bug.
(from Python’s “Functional Programming HowTo”)
# Convert to lowercaseEasy case: found typo in punctuation removal code. Fix the error, add comment like # Remove punctuation
First “application” of FP thinking: transform these comments into function names (why?)
Hard case: Something in load_text() modifies a variable that later on breaks remove_punct() (Called a side-effect)
Second “application” of FP thinking: Get rid of these side effects! (why?)
remove_punct()!!! 😎 ⏱️ = 💰The title relates to a classic Economics joke (the best kind of joke): “An economist and a CEO are walking down the street, when the CEO points at the ground and tells the economist, ‘look! A $20 bill on the ground!’ The economist keeps on walking, scoffing at the CEO: ‘don’t be silly, if there was a $20 bill on the ground, somebody would have picked it up already’.”
Pro-FP argument: “80% of outcomes are the result of 20% of inputs”
Specifically: one argument made for FP is that imperative and/or Object-Oriented* code leads to
20% of cases (corner cases) causing 80% of issues with code
Corner cases = cases where we have to “open up” a function and “trace” an issue line-by-line:
FP goal: Function signatures tell their full story (“success” in FP \(\propto\) never have to look “inside” a function!)
def get_first_character(s):
return s[0]Sounds good to me!
get_first_character("Hello")'H'
So far so good…
get_first_character("")--------------------------------------------------------------------------- IndexError Traceback (most recent call last) Cell In[6], line 1 ----> 1 get_first_character("") Cell In[4], line 2, in get_first_character(s) 1 def get_first_character(s): ----> 2 return s[0] IndexError: string index out of range
def divide(a, b):
return a / bOk, I’m a little scared after the last slide, but I’ll try to trust this function… 🙈
divide(2, 4), divide(4, 2)(0.5, 2.0)
Perhaps I can trust this function after all? 🥹
divide(2, 0)--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[9], line 1 ----> 1 divide(2, 0) Cell In[7], line 2, in divide(a, b) 1 def divide(a, b): ----> 2 return a / b ZeroDivisionError: division by zero
[In FP] we focus on functions that don’t lie. We want their signatures to tell the whole story about the body (Plachta 2022)
An Object-Oriented Tip Calculator:
class TipCalculator:
def __init__(self):
self.names = []
self.tip_percentage = 0
def add_person(self, name: str):
self.names.append(name)
if(len(self.names) > 5):
self.tip_percentage = 20
else:
self.tip_percentage = 10
def get_names(self) -> list[str]:
return self.names
def get_tip_percentage(self) -> int:
return self.tip_percentage
my_calc = TipCalculator()
my_calc.add_person("Jeff")
my_calc.get_tip_percentage()10
for i in range(5): my_calc.add_person(f'Jeff{i}')
my_calc.get_tip_percentage()20
A Functional Tip Calculator:
class TipCalculatorFP:
def add_person(
names: list[str], name: str
) -> list[str]:
updated = names.copy()
updated.append(name)
return updated
def get_tip_percentage(
names: list[str]
) -> int:
if len(names) > 5:
return 20
elif len(names) > 0:
return 10
else:
return 0
names = []
names = TipCalculatorFP.add_person(names, "Jeff")
TipCalculatorFP.get_tip_percentage(names)10
for i in range(5): names = TipCalculatorFP.add_person(names, f'Jeff{i}')
TipCalculatorFP.get_tip_percentage(names)20
from toolz.functoolz import pipe
add_jeff = lambda input: TipCalculatorFP.add_person(input, "Jeff")
pipe(
[],
add_jeff,
TipCalculatorFP.get_tip_percentage
)10
Do we need to name a whole new function each time we want to add a name? No!
pipe(
[],
lambda input: TipCalculatorFP.add_person(input, "Jeff"),
TipCalculatorFP.get_tip_percentage
)10
import os
os.environ['exponent'] = '2'
def compute_powers(my_nums: list[int]) -> list[int] | list[float]:
return [n ** int(os.environ['exponent']) for n in my_nums]
compute_powers([1, 2, 3])[1, 4, 9]
compute_powers()) commit code and go to sleep 😴import dotenv
dotenv.load_dotenv(override=True)
compute_powers([1, 2, 3])[1, 8, 27]
exponent argument would tell user they need to explicitly specify exponent before using your function!)exponent environment variable)def add(a, b):
with open('log.txt', 'w', encoding='utf-8') as logfile:
logfile.write(f'User adding {a} and {b}')
return a + bdef parse_money_amount(money_amount: str) -> float:
return float(money_amount.replace(",",""))
parse_money_amount("1,000,000")1000000.0
parse_money_amount("1'000'000")--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[20], line 1 ----> 1 parse_money_amount("1'000'000") Cell In[19], line 2, in parse_money_amount(money_amount) 1 def parse_money_amount(money_amount: str) -> float: ----> 2 return float(money_amount.replace(",","")) ValueError: could not convert string to float: "1'000'000"
