class: center, middle # CS 9B - Lab 3 ## Functions & Misc --- # Today's Topics **Lab 3.1:** Working with Dates - `datetime` module, `strftime()`, `timedelta` **Lab 3.2:** Unique Random Numbers - `random` module, sets, global variables **Lab 3.3:** Functional Operations on Iterables - `map()`, `filter()`, `reduce()` **Lab 3.4:** The Compose Function - Function composition, returning functions --- class: center, middle # Lab 3.1: Working with Dates ## The datetime Module --- # The datetime Module Python's `datetime` module provides classes for manipulating dates and times. ```python from datetime import date, timedelta # Create a date object d = date(2022, 7, 4) # July 4, 2022 # Parse a date from a string date_str = "2022-07-04" d = date.fromisoformat(date_str) # Get today's date today = date.today() ``` --- # Formatting Dates with strftime() `strftime()` formats a date object as a string: ```python from datetime import date d = date(2022, 7, 4) # Different format codes print(d.strftime("%m/%d/%Y")) # 07/04/2022 print(d.strftime("%B %d, %Y")) # July 04, 2022 print(d.strftime("%A")) # Monday ``` **Common format codes:** - `%m` - Month (zero-padded): 07 - `%d` - Day (zero-padded): 04 - `%Y` - Year (4 digits): 2022 - `%B` - Full month name: July - `%A` - Full weekday name: Monday --- # Date Arithmetic with timedelta `timedelta` represents a duration of time: ```python from datetime import date, timedelta d = date(2022, 7, 4) # Add 3 weeks to a date future = d + timedelta(weeks=3) print(future) # 2022-07-25 # Subtract dates to get difference d1 = date(2022, 7, 29) d2 = date(2022, 7, 4) diff = d1 - d2 print(diff.days) # 25 ``` --- # Lab 3.1 - Problem Statement 1. Complete `read_date()` to read a date string in format `yyyy-mm-dd` 2. Read 4 unique dates into a list 3. Sort the dates (earliest first) 4. Output sorted dates in format `mm/dd/yyyy` 5. Output days between the last two dates 6. Output date 3 weeks from most recent in format `"July 4, 1776"` 7. Output the weekday name of the earliest date .left-column[ **Example Input:** ``` 2022-01-27 2022-07-04 2020-12-31 2022-07-29 ``` ] .right-column[ **Expected Output:** ``` 12/31/2020 01/27/2022 07/04/2022 07/29/2022 25 August 19, 2022 Thursday ``` ] --- # Lab 3.1 - Approach ```python def read_date() -> date: date_str = input() # TODO: return a date from the string return def lab3_1() -> None: # 1. Read 4 dates into a list dates = [read_date() for _ in range(4)] # 2. Sort the dates sorted_dates = sorted(dates) # 3. Print in mm/dd/yyyy format for d in sorted_dates: # TODO: print the date in mm/dd/yyyy format # 4-7. Continue with remaining tasks... ``` --- class: center, middle # Lab 3.2: Unique Random Numbers ## The random Module --- # The random Module Python's `random` module generates pseudo-random numbers: ```python import random # Set seed for reproducibility random.seed(42) # Generate random integer in range [0, 10] num = random.randint(0, 10) # Generate multiple random numbers for _ in range(5): print(random.randint(0, 100)) ``` **Note:** Same seed produces same sequence of "random" numbers! --- # Using Sets for Uniqueness Sets automatically handle duplicates: ```python unique_numbers = set() unique_numbers.add(5) unique_numbers.add(3) unique_numbers.add(5) # Duplicate - ignored! print(unique_numbers) # {3, 5} print(len(unique_numbers)) # 2 ``` --- # Global Variables Use `global` keyword to modify variables outside a function: ```python retries = 0 # Global variable def generate_numbers(): global retries # Declare intent to modify # Now we can modify retries retries += 1 ``` **Important:** Without `global`, Python creates a new local variable! --- # Lab 3.2 - Problem Statement Write `unique_random_ints(how_many, max_num)` that: 1. Generates a set of `how_many` unique random integers from 0 to `max_num` 2. Counts retries (when a duplicate is generated) 3. Returns `(set_of_numbers, retry_count)` **Example:** If `how_many = 5` and `max_num = 7`, then the return should be something like: ```python ({1, 7, 0, 3, 2}, 4) ``` This means the function found the unique integers 1, 7, 0, 3, 2 and had to retry 4 times. **Note:** Do not set random seed inside the function! --- class: center, middle # Lab 3.3: Functional Operations ## map(), filter(), reduce() --- # Lambda Functions A **lambda** is a small anonymous function: ```python # Lambda syntax lambda x: x * 2 ``` This is equivalent to: ```python def double(x): return x * 2 ``` **Key points:** - Lambdas are single expressions (no statements) - Useful for short, throwaway functions - Often used with `map()`, `filter()`, `reduce()` --- # map() - Transform Each Element `map(function, iterable)` applies a function to each element: ```python numbers = [1, 2, 3, 4, 5] # Double each number doubled = list(map(lambda x: x * 2, numbers)) print(doubled) # [2, 4, 6, 8, 10] # Square each number squared = list(map(lambda x: x ** 2, numbers)) print(squared) # [1, 4, 9, 16, 25] ``` --- # filter() - Keep Matching Elements `filter(function, iterable)` keeps elements where function returns True: ```python numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # Keep only even numbers evens = list(filter(lambda x: x % 2 == 0, numbers)) print(evens) # [2, 4, 6, 8, 10] # Keep numbers greater than 5 big = list(filter(lambda x: x > 5, numbers)) print(big) # [6, 7, 8, 9, 10] ``` --- # reduce() - Combine All Elements `reduce(function, iterable, initial)` combines elements into one value: ```python from functools import reduce numbers = [1, 2, 3, 4, 5] # Sum all numbers (starting from 0) total = reduce(lambda acc, x: acc + x, numbers, 0) print(total) # 15 # Product of all numbers (starting from 1) product = reduce(lambda acc, x: acc * x, numbers, 1) print(product) # 120 ``` **Note:** Must import `reduce` from `functools`! --- # Lab 3.3 - Problem Statement Implement `magic_math(input_list)` using map, filter, and reduce: 1. **Map:** If `n` is odd, replace with `3n + 1` 2. **Map:** If `n` is even, replace with `n / 2` 3. **Filter:** Keep only values NOT divisible by 3 4. **Reduce:** Starting from 0: - Add the number if odd - Add half the number if even --- class: center, middle # Lab 3.4: The Compose Function ## Returning Functions --- # Functions as First-Class Objects In Python, functions can be: - Assigned to variables - Passed as arguments - Returned from other functions ```python def greet(name): return f"Hello, {name}!" # Assign function to variable say_hello = greet print(say_hello("Alice")) # Hello, Alice! ``` --- # Function Composition Composition combines two functions: h(x) = f(g(x)) ```python def f(x): return x + 1 def g(x): return x * 2 # Manual composition result = f(g(3)) ``` **Goal:** Create a function that returns the composed function! --- # Lab 3.4 - Problem Statement Create `compose(func_f, func_g)` that: - Takes two functions `f` and `g` - Returns a new function `h` where `h(x) = f(g(x))` ```python def f(x): return x + 1 def g(x): return x * 2 h = compose(f, g) print(h(3)) # f(g(3)) = f(6) = 7 ``` **Key insight:** We return a .blue[function], not a value! --- # Testing Your Code ```python if __name__ == '__main__': # Test Lab 3.1 lab3_1() # Test Lab 3.2 import random random.seed(42) print(unique_random_ints(5, 7)) # Test Lab 3.3 print(magic_math([1, 2, 3, 4, 5])) # Test Lab 3.4 f = lambda x: x + 1 g = lambda x: x * 2 h = compose(f, g) print(h(3)) # 7 ``` --- class: center, middle # Submission Reminder --- # Submitting to Gradescope Submit your solution to **Gradescope** by the deadline. **File name:** `lab3.py` **Tips:** - Double-check your file name before submitting - Read the error messages from Gradescope carefully - You can resubmit as many times as needed before the deadline - Don't set random seed inside `unique_random_ints()` --- class: center, middle # Questions? Good luck with Lab 3!