diff --git a/index.md b/index.md index f59167d..145f8c5 100644 --- a/index.md +++ b/index.md @@ -3,6 +3,11 @@ Idea from [this post](https://www.edwinwenink.xyz/posts/42-vim_notetaking/). This file gets generated by [this script](index.py). +## Algorithms + +- [Monotonic stack for finding largest k-digit number](notes/20251218125423.md) +- [Using modulo for circular wrapping](notes/20251218125421.md) + ## Api - [Using assembly ai to transcribe podcast audio files](notes/20240412111523.md) @@ -47,6 +52,7 @@ This file gets generated by [this script](index.py). - [Caching api calls](notes/20230130103011.md) - [Easy caching in python ๐Ÿ ๐Ÿ˜](notes/20231212063125.md) +- [Memoization with frozenset for immutable state](notes/20251218125431.md) ## Chainmap @@ -67,6 +73,7 @@ This file gets generated by [this script](index.py). ## Collections +- [Counter for tracking multiple states](notes/20251218125427.md) - [Counter vs defaultdict(int) performance](notes/20250722185526.md) - [Effortless grouping with defaultdict + geo mapping ๐ŸŒ](notes/20250606150610.md) - [How counter and defaultdict work in python](notes/20250602145533.md) @@ -109,8 +116,13 @@ This file gets generated by [this script](index.py). - [Dataclass field and post_init](notes/20240627192941.md) - [Dataclass with extra construction](notes/20240223145038.md) +- [Frozen dataclasses and itertools.pairwise](notes/20251218125429.md) - [Sqlalchemy model from dataclass](notes/20220907132243.md) +## Datastructures + +- [Using namedtuple for grid coordinates](notes/20251218125424.md) + ## Datetime - [From date string to datetime object](notes/20220914111908.md) @@ -141,6 +153,10 @@ This file gets generated by [this script](index.py). - [Timeit decorator](notes/20221026124022.md) - [Writing decorators function- vs class-based](notes/20240508134342.md) +## Dependencies + +- [Pep 723 inline script metadata](notes/20251218125432.md) + ## Deque - [Fifo vs lifo](notes/20250917125957.md) @@ -255,6 +271,14 @@ This file gets generated by [this script](index.py). - [Avoid mutable default arguments](notes/20220910100519.md) - [Iterators are consumed once](notes/20250729112057.md) +## Graphs + +- [Memoization with frozenset for immutable state](notes/20251218125431.md) + +## Grids + +- [Using namedtuple for grid coordinates](notes/20251218125424.md) + ## Heapq - [Heap queues](notes/20240209140325.md) @@ -264,6 +288,10 @@ This file gets generated by [this script](index.py). - [There is a module for everything in python!](notes/20220904164714.md) +## Idioms + +- [Parsing with tuple and map](notes/20251218125425.md) + ## Images - [Use imageio to make a gif image](notes/20220908100741.md) @@ -289,8 +317,10 @@ This file gets generated by [this script](index.py). - [Easily iterate over pairs in a sequence](notes/20231216211604.md) - [Flatten a list of list](notes/20221130102028.md) +- [Frozen dataclasses and itertools.pairwise](notes/20251218125429.md) - [Itertools.count](notes/20220906094357.md) - [Itertools.cycle](notes/20240126101005.md) +- [Sorting with custom key functions](notes/20251218125428.md) - [Split a sequence into pairs](notes/20231216210001.md) - [Use itertools + random to simulate rolling dice](notes/20240702100917.md) @@ -323,6 +353,14 @@ This file gets generated by [this script](index.py). - [Least common multiple](notes/20221213094557.md) +## Matrix + +- [Matrix transposition with zip](notes/20251218125426.md) + +## Modulo + +- [Using modulo for circular wrapping](notes/20251218125421.md) + ## Namedtuple - [Namedtuple + type hints](notes/20221206080616.md) @@ -351,6 +389,10 @@ This file gets generated by [this script](index.py). - [Operator.itemgetter](notes/20220905183749.md) - [Turn strings into operations (without eval)](notes/20221213095810.md) +## Optimization + +- [Using z3 solver for optimization problems](notes/20251218125430.md) + ## Packaging - [Determining a module's source](notes/20240108183829.md) @@ -359,6 +401,10 @@ This file gets generated by [this script](index.py). - [Install a package in editable mode](notes/20231215082936.md) - [Make an entry point to your python package](notes/20230817110202.md) +## Parsing + +- [Parsing with tuple and map](notes/20251218125425.md) + ## Pathlib - [Os vs pathlib](notes/20230824175324.md) @@ -368,10 +414,18 @@ This file gets generated by [this script](index.py). - [Read / write files the modern way](notes/20221202131250.md) - [Split file name and extension](notes/20240711112258.md) +## Patterns + +- [Detecting repeated patterns with string multiplication](notes/20251218125422.md) + ## Pdf - [Merge pdf files](notes/20240711112142.md) +## Pep723 + +- [Pep 723 inline script metadata](notes/20251218125432.md) + ## Performance - [More performant string building](notes/20231218133029.md) @@ -459,15 +513,27 @@ This file gets generated by [this script](index.py). - [A powerful parser](notes/20240212164917.md) +## Simulation + +- [Counter for tracking multiple states](notes/20251218125427.md) + ## Slicing - [Look for multiple indices in a list](notes/20221201102028.md) - [Split a sequence into pairs](notes/20231216210001.md) +## Sorting + +- [Sorting with custom key functions](notes/20251218125428.md) + ## Sqlalchemy - [Sqlalchemy model from dataclass](notes/20220907132243.md) +## Stack + +- [Monotonic stack for finding largest k-digit number](notes/20251218125423.md) + ## Staticmethod - [Staticmethod decorator](notes/20240123130230.md) @@ -494,6 +560,7 @@ This file gets generated by [this script](index.py). - [5 things you might not know f-strings can do ๐Ÿ’ก ๐Ÿงต](notes/20230829122531.md) - [Alphabet constants](notes/20221203180451.md) - [Bytes to string and vice versa](notes/20220912092040.md) +- [Detecting repeated patterns with string multiplication](notes/20251218125422.md) - [From messy to clean numbers with `removeprefix` / `removesuffix`](notes/20251120071001.md) - [More performant string building](notes/20231218133029.md) - [Removesuffix and removeprefix](notes/20240124191423.md) @@ -576,6 +643,10 @@ This file gets generated by [this script](index.py). - [Get a youtube video transcript](notes/20240410190949.md) +## Z3 + +- [Using z3 solver for optimization problems](notes/20251218125430.md) + ## Zen - [Although practicality beats purity.](notes/20231215184629.md) @@ -591,6 +662,10 @@ This file gets generated by [this script](index.py). - [Unless explicitly silenced](notes/20231218175630.md) - [Zip() got a strict keyword arg](notes/20220908171538.md) +## Zip + +- [Matrix transposition with zip](notes/20251218125426.md) + ## Zlib - [Zlib for data compression](notes/20220913194645.md) diff --git a/notes/20251218125421.md b/notes/20251218125421.md new file mode 100644 index 0000000..17262d6 --- /dev/null +++ b/notes/20251218125421.md @@ -0,0 +1,29 @@ +# Using modulo for circular wrapping + +Clean way to handle circular/wrapping behavior in #Python ๐Ÿ‘‡ + +Instead of complex if-else logic to wrap around, use the modulo operator: + +```python +DIAL_SIZE = 100 + +def step(pos: int, rotation: str, amount: int) -> int: + """Move position on a circular dial.""" + delta = amount if rotation == "R" else -amount + return (pos + delta) % DIAL_SIZE + +# Example usage: +pos = 95 +pos = step(pos, "R", 10) # 95 + 10 = 105 % 100 = 5 +print(pos) # 5 + +pos = 3 +pos = step(pos, "L", 5) # 3 + (-5) = -2 % 100 = 98 +print(pos) # 98 +``` + +Works for both positive and negative deltas - Python's modulo handles negatives correctly! + +Perfect for: game boards, circular buffers, rotating arrays, clock arithmetic. + +#algorithms #modulo diff --git a/notes/20251218125422.md b/notes/20251218125422.md new file mode 100644 index 0000000..e69a150 --- /dev/null +++ b/notes/20251218125422.md @@ -0,0 +1,30 @@ +# Detecting repeated patterns with string multiplication + +Clever trick to check if a string is made of repeated chunks in #Python ๐Ÿ‘‡ + +```python +def has_repeated_chunks(s: str) -> bool: + """Check if string is composed of a repeating pattern.""" + n = len(s) + for size in range(1, n // 2 + 1): + # Skip if size doesn't divide evenly + if n % size != 0: + continue + + chunk = s[:size] + # Multiply chunk and compare to original + if chunk * (n // size) == s: + return True + + return False + +# Examples: +print(has_repeated_chunks("abcabc")) # True (abc repeated 2 times) +print(has_repeated_chunks("123123123")) # True (123 repeated 3 times) +print(has_repeated_chunks("abcdef")) # False +print(has_repeated_chunks("aaaaaa")) # True (a repeated 6 times) +``` + +String multiplication (`chunk * count`) creates a repeated pattern - compare it to the original to detect repetition! + +#strings #patterns diff --git a/notes/20251218125423.md b/notes/20251218125423.md new file mode 100644 index 0000000..93b7d77 --- /dev/null +++ b/notes/20251218125423.md @@ -0,0 +1,30 @@ +# Monotonic stack for finding largest K-digit number + +Powerful algorithm to find the largest K-digit number by removing digits in #Python ๐Ÿ‘‡ + +```python +def find_largest_k_digit_number(digits: str, k: int) -> str: + """Find largest K-digit number by removing digits.""" + num_drops = len(digits) - k + stack = [] + + for digit in digits: + # Remove smaller digits from stack while we can + while num_drops and stack and stack[-1] < digit: + stack.pop() + num_drops -= 1 + stack.append(digit) + + return "".join(stack[:k]) + +# Examples: +print(find_largest_k_digit_number("1432219", 3)) # "439" +print(find_largest_k_digit_number("10200", 2)) # "22" +print(find_largest_k_digit_number("54321", 2)) # "54" +``` + +The stack maintains potential candidates. Greedily remove smaller digits when we see a larger one. + +Time complexity: O(n). Great for competitive programming! + +#algorithms #stack diff --git a/notes/20251218125424.md b/notes/20251218125424.md new file mode 100644 index 0000000..6d84919 --- /dev/null +++ b/notes/20251218125424.md @@ -0,0 +1,34 @@ +# Using NamedTuple for grid coordinates + +Clean, memory-efficient way to represent positions in #Python ๐Ÿ‘‡ + +```python +from typing import NamedTuple + +class Position(NamedTuple): + x: int + y: int + +# 8 directions for grid navigation +DIRECTIONS = [ + Position(-1, -1), Position(0, -1), Position(1, -1), + Position(-1, 0), Position(1, 0), + Position(-1, 1), Position(0, 1), Position(1, 1), +] + +def get_neighbors(pos: Position) -> list[Position]: + """Get all 8 neighbors of a position.""" + return [Position(pos.x + d.x, pos.y + d.y) for d in DIRECTIONS] + +# Usage: +current = Position(5, 5) +neighbors = get_neighbors(current) + +# NamedTuple benefits: +# โœ“ Immutable (hashable - works in sets/dict keys) +# โœ“ Memory efficient (uses __slots__) +# โœ“ Named access (pos.x, pos.y) +# โœ“ Tuple unpacking (x, y = pos) +``` + +#datastructures #grids diff --git a/notes/20251218125425.md b/notes/20251218125425.md new file mode 100644 index 0000000..0605303 --- /dev/null +++ b/notes/20251218125425.md @@ -0,0 +1,30 @@ +# Parsing with tuple and map + +Concise pattern for parsing structured data in #Python ๐Ÿ‘‡ + +```python +# Parse ranges from strings +def parse_ranges(text: str) -> list[tuple[int, int]]: + """Parse lines like '10-20' into tuples of integers.""" + return [tuple(map(int, line.split("-"))) for line in text.splitlines()] + +# Example usage: +data = """10-20 +30-45 +100-150""" + +ranges = parse_ranges(data) +print(ranges) +# [(10, 20), (30, 45), (100, 150)] + +# Check if value is in any range: +def in_range(value: int, ranges: list[tuple[int, int]]) -> bool: + return any(start <= value <= end for start, end in ranges) + +print(in_range(15, ranges)) # True (in 10-20) +print(in_range(25, ranges)) # False +``` + +`tuple(map(int, ...))` is more memory efficient than list comprehension for fixed-size sequences. + +#parsing #idioms diff --git a/notes/20251218125426.md b/notes/20251218125426.md new file mode 100644 index 0000000..b2cd775 --- /dev/null +++ b/notes/20251218125426.md @@ -0,0 +1,34 @@ +# Matrix transposition with zip + +Elegant way to transpose a matrix (swap rows/columns) in #Python ๐Ÿ‘‡ + +```python +from math import prod + +# Original grid (rows) +grid = [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12] +] + +# Transpose: rows become columns +transposed = list(zip(*grid)) +print(transposed) +# [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)] + +# Practical example: column operations +ops = ["*", "+", "*", "+"] + +# Process each column with its operation +result = sum( + prod(col) if op == "*" else sum(col) + for op, col in zip(ops, transposed) +) +print(result) # (1*5*9) + (2+6+10) + (3*7*11) + (4+8+12) = 45 + 18 + 231 + 24 + +# Remember: zip(*matrix) unpacks rows as arguments to zip +# which pairs up elements at same index = columns! +``` + +#matrix #zip diff --git a/notes/20251218125427.md b/notes/20251218125427.md new file mode 100644 index 0000000..5be1d28 --- /dev/null +++ b/notes/20251218125427.md @@ -0,0 +1,35 @@ +# Counter for tracking multiple states + +Use Counter to track quantities across multiple states in #Python ๐Ÿ‘‡ + +```python +from collections import Counter + +def simulate_beams(grid: list[str]) -> int: + """Simulate light beams splitting as they move through grid.""" + # Track number of beams at each column position + beams = Counter({0: 1}) # Start with 1 beam at column 0 + total_splits = 0 + + for row in grid: + new_beams = Counter() + for col, count in beams.items(): + if row[col] == "^": # Splitter + total_splits += 1 + # Beam splits into two + new_beams[col - 1] += count + new_beams[col + 1] += count + else: # Continue straight + new_beams[col] += count + beams = new_beams + + return total_splits + +# Example: +grid = [".....", "..^..", ".....", ".^..."] +print(simulate_beams(grid)) # Counts beam splits +``` + +Counter arithmetic (`+=`) handles aggregation automatically. Perfect for simulations! + +#collections #simulation diff --git a/notes/20251218125428.md b/notes/20251218125428.md new file mode 100644 index 0000000..3b4651b --- /dev/null +++ b/notes/20251218125428.md @@ -0,0 +1,35 @@ +# Sorting with custom key functions + +Powerful pattern: generate pairs, sort by custom metric in #Python ๐Ÿ‘‡ + +```python +from itertools import combinations +from math import dist + +# Find closest pairs of coordinates +coords = [(0, 0), (1, 1), (5, 5), (1, 0)] + +# Generate all pairs and sort by distance +pairs_by_distance = sorted( + combinations(coords, 2), + key=lambda pair: dist(*pair) +) + +for (a, b), distance in [(p, dist(*p)) for p in pairs_by_distance]: + print(f"{a} to {b}: {distance:.2f}") + +# Output: +# (0, 0) to (1, 0): 1.00 +# (0, 0) to (1, 1): 1.41 +# (1, 0) to (1, 1): 1.00 +# (1, 1) to (5, 5): 5.66 +# (0, 0) to (5, 5): 7.07 +# (1, 0) to (5, 5): 6.40 + +# This pattern is great for: +# - Greedy algorithms (process closest/smallest/largest first) +# - Finding optimal pairings +# - Clustering algorithms +``` + +#itertools #sorting diff --git a/notes/20251218125429.md b/notes/20251218125429.md new file mode 100644 index 0000000..2820681 --- /dev/null +++ b/notes/20251218125429.md @@ -0,0 +1,43 @@ +# Frozen dataclasses and itertools.pairwise + +Combining immutable dataclasses with pairwise iteration in #Python ๐Ÿ‘‡ + +```python +from dataclasses import dataclass +from itertools import pairwise + +@dataclass(frozen=True) +class Point: + x: int + y: int + +# Polygon vertices +polygon = [ + Point(0, 0), + Point(5, 0), + Point(5, 3), + Point(0, 3), +] + +# Get edges connecting consecutive points +# pairwise creates (p0,p1), (p1,p2), (p2,p3), ... +edges = list(pairwise(polygon)) + +for start, end in edges: + print(f"Edge from {start} to {end}") + +# Close the polygon (connect last to first) +closed_edges = list(pairwise(polygon + [polygon[0]])) + +# frozen=True makes instances: +# โœ“ Immutable (safer) +# โœ“ Hashable (can use in sets/dicts) +# โœ“ Thread-safe + +# pairwise is perfect for: +# - Polygon edges +# - Time series deltas +# - State transitions +``` + +#dataclasses #itertools diff --git a/notes/20251218125430.md b/notes/20251218125430.md new file mode 100644 index 0000000..8f74db0 --- /dev/null +++ b/notes/20251218125430.md @@ -0,0 +1,49 @@ +# Using Z3 solver for optimization problems + +Solve complex constraint problems with z3-solver in #Python ๐Ÿ‘‡ + +```python +# /// script +# dependencies = [ +# "z3-solver", +# ] +# /// +import z3 + +def solve_button_puzzle(buttons: list[tuple[int, ...]], target: list[int]) -> int: + """Find minimum button presses to reach target state. + + Each button affects multiple counters when pressed. + Find minimum total presses to reach exact target values. + """ + optimizer = z3.Optimize() + + # Create variables for number of times each button is pressed + presses = [z3.Int(f"button_{i}") for i in range(len(buttons))] + + # Constraint: can't press negative times + for p in presses: + optimizer.add(p >= 0) + + # Constraint: each counter must equal its target + for counter_idx, target_value in enumerate(target): + total = sum( + presses[btn_idx] + for btn_idx, button in enumerate(buttons) + if counter_idx in button + ) + optimizer.add(total == target_value) + + # Minimize total presses + optimizer.minimize(sum(presses)) + optimizer.check() + + return optimizer.model().eval(sum(presses)).as_long() + +# Example: +buttons = [(0, 1), (1, 2), (0, 2)] # Which counters each button affects +target = [3, 5, 2] # Desired counter values +print(solve_button_puzzle(buttons, target)) +``` + +#z3 #optimization diff --git a/notes/20251218125431.md b/notes/20251218125431.md new file mode 100644 index 0000000..1be377a --- /dev/null +++ b/notes/20251218125431.md @@ -0,0 +1,46 @@ +# Memoization with frozenset for immutable state + +Use functools.cache with frozenset to memoize graph traversal in #Python ๐Ÿ‘‡ + +```python +from functools import cache + +def count_paths(graph: dict[str, list[str]], + start: str, + target: str, + must_visit: set[str]) -> int: + """Count paths that visit required nodes.""" + + @cache + def search(node: str, visited: frozenset[str]) -> int: + # Update visited set if this is a required node + if node in must_visit: + visited = visited | {node} + + # Success: reached target with all required nodes + if node == target: + return int(must_visit.issubset(visited)) + + # Recursively explore children + return sum(search(child, visited) for child in graph[node]) + + return search(start, frozenset()) + +# Example: +graph = { + "A": ["B", "C"], + "B": ["D"], + "C": ["D"], + "D": [] +} + +# Count paths from A to D that go through B +print(count_paths(graph, "A", "D", {"B"})) # 1 + +# frozenset is: +# โœ“ Immutable (hashable for caching) +# โœ“ Perfect for visited/seen tracking +# โœ“ Supports set operations (|, &, -, etc.) +``` + +#caching #graphs diff --git a/notes/20251218125432.md b/notes/20251218125432.md new file mode 100644 index 0000000..547affe --- /dev/null +++ b/notes/20251218125432.md @@ -0,0 +1,39 @@ +# PEP 723 inline script metadata + +Declare script dependencies directly in your Python file with PEP 723 ๐Ÿ‘‡ + +```python +# /// script +# dependencies = [ +# "requests", +# "pandas>=2.0", +# "numpy", +# ] +# /// + +import requests +import pandas as pd +import numpy as np + +def fetch_and_process(url: str) -> pd.DataFrame: + """Fetch data and process it.""" + response = requests.get(url) + data = response.json() + return pd.DataFrame(data) + +# Run with uv, pipx, or other PEP 723-compatible runners: +# $ uv run script.py +# $ pipx run script.py + +# Benefits: +# โœ“ No separate requirements.txt needed +# โœ“ Dependencies are self-documenting +# โœ“ Works with modern Python runners +# โœ“ Great for single-file scripts and utilities +``` + +Perfect for standalone scripts, Advent of Code solutions, and quick utilities! + +Supported by: uv, pipx, hatch, and other modern Python tools. + +#pep723 #dependencies