➜ ~/python-101 python3 learn.py --level beginner
import python_101
>>> "Cheat Sheet"▋
You already have an idea for how to solve the problem. This course gives you the Python tools to write it down — fast input, handy built-ins, the standard-library helpers that save you writing code, plus reading and writing files and scraping websites.
How to read the coloured boxes: # analogy # tip # watch out
Prerequisites
You don't need to be a programmer yet. You need a few things on your computer and a couple of ideas in your head.
🧰 Tools
- Python 3.10 or newer from python.org
- A code editor — VS Code with the Python extension is a good free choice
- A terminal (Terminal on macOS/Linux, PowerShell on Windows)
- For the scraping section: pip install requests beautifulsoup4
🧠 Ideas
- A program is a list of instructions run top to bottom.
- A variable is a name for a value.
- A file lives in a folder, and has a path like docs/notes.txt.
- Basic maths: +, −, ×, ÷ and remainders.
Check your setup
python3 --version # Windows: py --version
python3 # opens the interactive "REPL"; type exit() to leave
python3 solution.py # runs a file
python3 solution.py < input.txt # feeds input.txt as if you typed itYour First Program
Create a file called hello.py, paste this in, and run python3 hello.py.
# Lines starting with # are comments: Python ignores them.
name = "Ada"
print("Hello,", name) # Hello, Ada
print(f"{name} has {len(name)} letters") # Ada has 3 lettersIndentation is grammar
Other languages use { } to group lines. Python uses indentation (4 spaces). A colon : means “an indented block comes next”.
for i in range(3):
print("inside the loop", i) # indented: repeats
print("after the loop") # not indented: runs onceVariables, Types & Operators
A variable is a name tag stuck onto a value. The value has a type: whole number, decimal number, text, true/false, or “nothing”.
DIAGRAM · Names point at values
b = a does not copy the list. Both tags now point at the same list, so changing it through b also changes what a sees.
age = 21 # int (whole number, any size)
price = 9.99 # float (decimal number)
name = "Ada" # str (text)
is_ready = True # bool (True / False)
nothing = None # NoneType ("no value yet")
print(type(age), type(price), type(name))
# Convert between types
int("42"), float("3.5"), str(10), bool(0), int(3.9) # 42 3.5 '10' False 3
# Several assignments at once, and the famous swap
x, y = 1, 2
x, y = y, x # x=2, y=1, no temp variable needed
a = b = 0 # both start at 0Arithmetic
| Operator | Meaning | Example | Result |
|---|---|---|---|
| + - * | add, subtract, multiply | 7 * 3 | 21 |
| / | divide (always gives a float) | 7 / 2 | 3.5 |
| // | floor divide (round down) | 7 // 2 | 3 |
| % | remainder (modulo) | 7 % 2 | 1 |
| ** | power | 2 ** 10 | 1024 |
| divmod | quotient and remainder together | divmod(7, 2) | (3, 1) |
count = 0
count += 1 # same as count = count + 1 (also -=, *=, //=, %=)
# Comparisons give True/False, and you can chain them
print(1 < 5 <= 5) # True (means 1 < 5 and 5 <= 5)
print(3 == 3.0, 3 != 4) # True True
# Logic words
print(True and False, True or False, not True) # False True FalseInput & Output (Fast I/O)
Every coding test starts by reading numbers. input() reads one line as text; you then cut it up and convert it.
n = int(input()) # "5" -> 5
a, b = map(int, input().split()) # "3 7" -> a=3, b=7
nums = list(map(int, input().split())) # "1 2 3 4" -> [1, 2, 3, 4]
word = input().strip() # strip removes spaces/newline at the ends
grid = [input() for _ in range(n)] # n lines of text
matrix = [list(map(int, input().split())) for _ in range(n)] # n rows of numbersFaster input for big tests
input() is slow when there are 10⁵+ lines. Swap it for sys.stdin.readline, or read everything in one go.
import sys
input = sys.stdin.readline # drop-in replacement (keeps the trailing "\n"!)
n = int(input())
s = input().strip() # .strip() matters for strings now
# Or: read ALL tokens at once and walk through them
data = sys.stdin.buffer.read().split()
n = int(data[0])
nums = list(map(int, data[1:1 + n]))Printing
nums = [1, 2, 3]
print(1, 2, 3) # 1 2 3
print(1, 2, 3, sep=",") # 1,2,3
print("no newline", end="") # stays on the same line
print()
print(*nums) # 1 2 3 (* unpacks the list)
print(" ".join(map(str, nums))) # 1 2 3 (join needs strings)
print("\n".join(map(str, nums))) # one per line
# Fast output: collect lines, print once
out = []
for i in range(3):
out.append(str(i * i))
print("\n".join(out))
# f-strings: the easiest formatting
pi = 3.14159265
print(f"{pi:.2f}") # 3.14 (2 decimal places)
print(f"{42:5d}|") # 42| (width 5)
print(f"{42:05d}") # 00042 (zero padded)
print(f"{1234567:,}") # 1,234,567
print(f"{'hi':>6}|{'hi':<6}|{'hi':^6}|") # right / left / centre
print(f"{255:b} {255:x} {255:o}") # 11111111 ff 377Strings
A string is a row of characters. Strings are immutable: you can't change a letter in place, you build a new string instead.
DIAGRAM · Indexing s = "PYTHON"
Green numbers count from the front (starting at 0). Pink numbers count from the back. s[1:4] means “from index 1 up to, but not including, 4” → "YTH".
s = "PYTHON"
s[0], s[-1] # 'P', 'N'
s[1:4] # 'YTH' start:stop (stop is excluded)
s[:2], s[2:] # 'PY', 'THON'
s[::2] # 'PTO' every 2nd char
s[::-1] # 'NOHTYP' reversed!
len(s) # 6
"TH" in s # True
t = " Hello, World "
t.strip() # 'Hello, World'
t.lower(), t.upper() # lower / UPPER case
"a,b,c".split(",") # ['a', 'b', 'c']
"-".join(["a", "b", "c"]) # 'a-b-c'
"banana".count("a") # 3
"banana".find("n") # 2 (-1 if missing)
"banana".index("n") # 2 (error if missing)
"banana".replace("a", "o") # 'bonono'
"hello".startswith("he") # True
"abc123".isalnum(), "123".isdigit(), "abc".isalpha() # True True True
"hello world".title() # 'Hello World'
# Characters <-> numbers (ASCII / Unicode code points)
ord("a"), chr(97) # 97, 'a'
ord("c") - ord("a") # 2 -> handy for "letter index" problems
# Changing a string = rebuild it via a list
chars = list("hello")
chars[0] = "j"
print("".join(chars)) # jelloimport string
string.ascii_lowercase # 'abcdefghijklmnopqrstuvwxyz'
string.ascii_uppercase # 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
string.digits # '0123456789'
# Repeat and multi-line
print("ab" * 3) # ababab
poem = """line one
line two"""Lists
A list is an ordered, changeable row of items — Python's “array”. Indexing and slicing work exactly like strings.
nums = [5, 3, 8]
nums.append(1) # [5, 3, 8, 1] add to end O(1)
nums.extend([7, 7]) # [5, 3, 8, 1, 7, 7] add many
nums.insert(0, 9) # [9, 5, 3, 8, 1, 7, 7] insert at index O(n)
nums.pop() # removes & returns last item O(1)
nums.pop(0) # removes first item O(n)
nums.remove(8) # removes first 8 found O(n)
nums.index(3) # position of first 3
nums.count(7) # how many 7s
nums.sort() # sorts IN PLACE, returns None
nums.reverse() # reverses IN PLACE
print(nums)
new = sorted(nums) # returns a NEW sorted list
copy = nums[:] # shallow copy (also nums.copy() or list(nums))
zeros = [0] * 5 # [0, 0, 0, 0, 0]
last_two = nums[-2:]
del nums[0] # delete by index
nums[1:3] = [100] # replace a sliceList comprehensions — loops in one line
squares = [x * x for x in range(6)] # [0, 1, 4, 9, 16, 25]
evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
labels = ["even" if x % 2 == 0 else "odd" for x in range(4)]
pairs = [(i, j) for i in range(2) for j in range(2)] # nested loops
flat = [v for row in [[1, 2], [3, 4]] for v in row] # [1, 2, 3, 4]2D grids (and the #1 beginner trap)
DIAGRAM · [[0]*3]*3 vs a comprehension
❌ [[0]*3]*3
─┼▶
─┘
Three tags, one list. Change one row and every row changes.
✅ [[0]*3 for _ in range(3)]
──▶
──▶
Three separate lists. Rows are independent.
bad = [[0] * 3] * 3
bad[0][0] = 1
print(bad) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] -- surprise!
rows, cols = 3, 4
grid = [[0] * cols for _ in range(rows)]
grid[0][0] = 1
print(grid) # only the first row changed
# Walk the 4 neighbours of a cell (very common in grid problems)
r, c = 1, 1
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols:
print("neighbour", nr, nc)
transposed = [list(col) for col in zip(*grid)] # swap rows and columnsTuples & Unpacking
A tuple is a list you can't change. Because it never changes, it can be used as a dictionary key or stored in a set — perfect for coordinates like (row, col).
point = (3, 4)
x, y = point # unpacking
single = (5,) # one-item tuple needs a comma
first, *rest = [1, 2, 3, 4] # first=1, rest=[2, 3, 4]
*init, last = [1, 2, 3, 4] # init=[1, 2, 3], last=4
a, _, c = (1, 2, 3) # _ means "I don't care about this one"
# Tuples compare item by item, left to right
print((1, 5) < (2, 0)) # True
print((1, 5) < (1, 6)) # True
visited = {(0, 0), (0, 1)} # set of coordinates
print((0, 1) in visited) # TrueDictionaries
A dictionary maps keys to values. Looking up a key is instant (O(1)) no matter how big the dict is.
ages = {"ada": 36, "alan": 41}
ages["grace"] = 85 # add / update
print(ages["ada"]) # 36 (KeyError if missing!)
print(ages.get("bob")) # None (safe)
print(ages.get("bob", 0)) # 0 (safe with default)
print("ada" in ages) # True (checks keys)
del ages["alan"]
ages.pop("grace", None) # remove if present
for name, age in ages.items():
print(name, age)
list(ages.keys()), list(ages.values())
# Counting by hand
freq = {}
for ch in "banana":
freq[ch] = freq.get(ch, 0) + 1
print(freq) # {'b': 1, 'a': 3, 'n': 2}
# Dict comprehension
sq = {x: x * x for x in range(4)} # {0: 0, 1: 1, 2: 4, 3: 9}
inverted = {v: k for k, v in sq.items()}
# Sort a dict by value (highest first)
top = sorted(freq.items(), key=lambda kv: kv[1], reverse=True)
print(top) # [('a', 3), ('n', 2), ('b', 1)]
# Merge two dicts
merged = {"a": 1} | {"b": 2} # Python 3.9+Sets
A set is a bag of unique items with instant “is it in here?” checks. Use it to remove duplicates or track what you've already seen.
DIAGRAM · Set operations on A = {1,2,3} and B = {2,3,4}
seen = set() # NOT {} -- that's an empty dict
seen.add(3)
seen.update([4, 5])
seen.discard(99) # no error if missing (remove() would error)
print(4 in seen) # True, in O(1)
unique = set([3, 1, 3, 2, 1]) # {1, 2, 3}
print(len(unique)) # count distinct values
A, B = {1, 2, 3}, {2, 3, 4}
print(A | B, A & B, A - B, A ^ B)
print({1, 2} <= A) # subset check: True
frozen = frozenset([1, 2]) # immutable set, can be a dict keyControl Flow & Loops
x = 7
if x > 10:
print("big")
elif x > 5:
print("medium")
else:
print("small")
label = "even" if x % 2 == 0 else "odd" # one-line if (ternary)
for i in range(5): print(i, end=" ") # 0 1 2 3 4
print()
for i in range(2, 10, 3): print(i, end=" ") # 2 5 8
print()
for i in range(5, 0, -1): print(i, end=" ") # 5 4 3 2 1
print()
n = 3
while n > 0:
n -= 1
if n == 1:
continue # skip to next round
print("n is", n)
for v in [1, 3, 5]:
if v % 2 == 0:
print("found even")
break # leave the loop early
else:
print("no even number") # runs only if the loop did NOT break
# Walrus operator := assigns inside an expression (3.8+)
data = [1, 2, 3, 4]
if (n := len(data)) > 3:
print(f"long list of {n}")
# match/case (3.10+): a tidy multi-way switch
cmd = "add"
match cmd:
case "add":
print("adding")
case "remove" | "delete":
print("removing")
case _:
print("unknown")Functions, Lambdas & Recursion
A function is a reusable recipe: it takes ingredients (arguments) and gives back a result (return).
def area(width, height=1): # height has a default value
"""Return the area of a rectangle.""" # docstring
return width * height
print(area(3, 4), area(5), area(height=2, width=3)) # 12 5 6
def min_max(nums):
return min(nums), max(nums) # return several values as a tuple
lo, hi = min_max([4, 1, 9])
def total(*args, **kwargs): # any number of positional / named args
return sum(args) + sum(kwargs.values())
print(total(1, 2, 3, bonus=10)) # 16
square = lambda x: x * x # tiny anonymous function
print(square(5)) # 25
# Functions inside functions, and changing an outer variable
def counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
c = counter(); c(); print(c()) # 2
# Type hints are optional labels -- Python doesn't enforce them
def greet(name: str) -> str:
return "Hi " + nameRecursion
A function that calls itself on a smaller version of the problem. It needs a base case to stop.
import sys
sys.setrecursionlimit(10**6) # default is ~1000: deep recursion would crash
def factorial(n):
if n <= 1: # base case
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120Built-in Power Pack
These need no import. Knowing them saves you writing loops by hand.
nums = [4, 1, 7, 3]
names = ["ann", "bob", "cy"]
for i, v in enumerate(nums): # index AND value
print(i, v)
for i, v in enumerate(nums, start=1): # count from 1
pass
for name, n in zip(names, nums): # walk two lists side by side
print(name, n) # stops at the shorter one
sum(nums), min(nums), max(nums), len(nums) # 15 1 7 4
max(names, key=len) # 'ann' (longest; first on ties)
min(nums, default=0) # safe on empty lists
any(x > 5 for x in nums) # True (at least one)
all(x > 0 for x in nums) # True (every one)
list(map(str, nums)) # ['4', '1', '7', '3']
list(filter(lambda x: x % 2, nums)) # [1, 7, 3] (keep odd)
list(reversed(nums)) # [3, 7, 1, 4]
abs(-5), round(2.675, 2), round(7.5) # 5 2.67 8
pow(2, 10), pow(2, 10, 1000) # 1024 24 (3rd arg = fast modulo)
divmod(17, 5) # (3, 2)
sorted("hello") # ['e', 'h', 'l', 'l', 'o']
isinstance(5, int) # True
int("ff", 16), int("101", 2) # 255 5 (parse other bases)
bin(5), hex(255), oct(8) # '0b101' '0xff' '0o10'
float("inf"), -float("inf") # infinities, great as starting min/maxSorting Like a Pro
The key= argument tells Python what to look at when comparing items. Python's sort is stable: equal items keep their original order.
people = [("ann", 30), ("bob", 25), ("cy", 30)]
sorted(people, key=lambda p: p[1]) # by age, ascending
sorted(people, key=lambda p: p[1], reverse=True) # by age, descending
sorted(people, key=lambda p: (-p[1], p[0])) # age desc, then name asc
sorted(["bb", "a", "ccc"], key=len) # by length
sorted(["b", "A", "c"], key=str.lower) # case-insensitive
from operator import itemgetter
sorted(people, key=itemgetter(1, 0)) # same as lambda p: (p[1], p[0])
# Custom compare function (when a key is hard to write)
from functools import cmp_to_key
def compare(a, b):
# return negative if a goes first, positive if b goes first, 0 if tie
return -1 if a + b > b + a else 1
nums = ["3", "30", "34", "5", "9"]
print("".join(sorted(nums, key=cmp_to_key(compare)))) # 9534330 (largest number)
# Indices that would sort a list
vals = [30, 10, 20]
order = sorted(range(len(vals)), key=lambda i: vals[i]) # [1, 2, 0]collections
Supercharged containers from the standard library. These four come up constantly.
Counter — count things instantly
from collections import Counter
c = Counter("mississippi")
print(c) # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
print(c["s"], c["z"]) # 4 0 (missing keys are 0, no error)
print(c.most_common(2)) # [('i', 4), ('s', 4)]
print(Counter("listen") == Counter("silent")) # True -> anagram check
c.update("ss") # add more counts
print(Counter([1, 1, 2]) - Counter([1])) # Counter({1: 1, 2: 1})defaultdict — dict with an automatic starting value
from collections import defaultdict
graph = defaultdict(list) # missing key -> starts as []
for a, b in [(1, 2), (1, 3), (2, 3)]:
graph[a].append(b)
graph[b].append(a)
print(dict(graph)) # {1: [2, 3], 2: [1, 3], 3: [1, 2]}
counts = defaultdict(int) # missing key -> starts as 0
for w in "a b a".split():
counts[w] += 1
groups = defaultdict(set) # missing key -> starts as set()deque — a list that's fast at both ends
DIAGRAM · Double-ended queue
popleft
⇄
pop
⇄
All four operations are O(1). A plain list's pop(0) is O(n) because every item has to shuffle left. Use a deque for queues (e.g. BFS).
from collections import deque
q = deque([1, 2, 3])
q.append(4); q.appendleft(0) # deque([0, 1, 2, 3, 4])
q.popleft(); q.pop() # removes 0 and 4
q.rotate(1) # deque([3, 1, 2])
print(q[0], q[-1]) # peek both ends
window = deque(maxlen=3) # keeps only the last 3 items
for x in range(6):
window.append(x)
print(list(window)) # [3, 4, 5]
# BFS skeleton
graph = {0: [1, 2], 1: [3], 2: [3], 3: []}
dist = {0: 0}
q = deque([0])
while q:
node = q.popleft()
for nxt in graph[node]:
if nxt not in dist:
dist[nxt] = dist[node] + 1
q.append(nxt)
print(dist) # {0: 0, 1: 1, 2: 1, 3: 2}namedtuple — tuples with field names
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y, p) # 3 4 Point(x=3, y=4)heapq: Priority Queues
A heap always gives you the smallest item fast, even while you keep adding new ones. It's stored in a normal list.
DIAGRAM · The list [1, 3, 2, 7, 4] as a min-heap tree
Every parent is ≤ its children, so the smallest is always at index 0. Children of index i live at 2i+1 and 2i+2. Push and pop are O(log n).
import heapq
h = []
heapq.heappush(h, 5)
heapq.heappush(h, 1)
heapq.heappush(h, 3)
print(h[0]) # 1 peek smallest, O(1)
print(heapq.heappop(h)) # 1 remove smallest, O(log n)
nums = [5, 7, 1, 3]
heapq.heapify(nums) # turn a list into a heap in O(n)
# Max-heap trick: store negatives
mx = []
for x in [5, 1, 9]:
heapq.heappush(mx, -x)
print(-heapq.heappop(mx)) # 9
# Tuples: sorted by first item, then second...
tasks = []
heapq.heappush(tasks, (2, "write"))
heapq.heappush(tasks, (1, "read"))
print(heapq.heappop(tasks)) # (1, 'read')
print(heapq.nlargest(2, [4, 1, 9, 7])) # [9, 7]
print(heapq.nsmallest(2, [4, 1, 9, 7])) # [1, 4]bisect: Binary Search
On a sorted list, bisect finds where a value would go in O(log n) — like opening a phone book in the middle instead of reading every page.
DIAGRAM · Where does 3 go in [1, 3, 3, 5]?
bisect_left = before existing 3s; bisect_right = after them. Their difference is how many 3s there are.
from bisect import bisect_left, bisect_right, insort
a = [1, 3, 3, 5]
print(bisect_left(a, 3), bisect_right(a, 3)) # 1 3
print(bisect_right(a, 3) - bisect_left(a, 3)) # 2 (count of 3s)
print(bisect_left(a, 4)) # 3 -> first index with value >= 4
print(bisect_right(a, 4) - 1) # 2 -> last index with value <= 4
i = bisect_left(a, 5)
found = i < len(a) and a[i] == 5 # "is 5 in the list?" in O(log n)
insort(a, 4) # insert keeping it sorted
print(a) # [1, 3, 3, 4, 5]
# Binary search on the answer with a key (Python 3.10+)
words = ["a", "bb", "ccc", "dddd"]
print(bisect_left(words, 3, key=len)) # 2 -> first word with len >= 3itertools: Combinatorics
Brute-force helpers. Instead of writing nested loops to try every arrangement, ask itertools.
from itertools import (permutations, combinations, combinations_with_replacement,
product, accumulate, groupby, chain, pairwise, count, islice)
list(permutations([1, 2, 3])) # all 6 orderings
list(permutations("abc", 2)) # ordered pairs: ('a','b'), ('a','c'), ...
list(combinations([1, 2, 3], 2)) # [(1, 2), (1, 3), (2, 3)] order doesn't matter
list(combinations_with_replacement([1, 2], 2)) # [(1, 1), (1, 2), (2, 2)]
list(product([0, 1], repeat=3)) # all 8 bit patterns of length 3
list(product("ab", [1, 2])) # [('a', 1), ('a', 2), ('b', 1), ('b', 2)]
list(accumulate([1, 2, 3, 4])) # [1, 3, 6, 10] prefix sums
list(accumulate([1, 2, 3, 4], initial=0)) # [0, 1, 3, 6, 10]
list(accumulate([3, 1, 4], max)) # [3, 3, 4] running max
# groupby groups CONSECUTIVE equal items -> run-length encoding
print([(k, len(list(g))) for k, g in groupby("aaabbc")]) # [('a', 3), ('b', 2), ('c', 1)]
list(chain([1, 2], [3], [4, 5])) # [1, 2, 3, 4, 5] glue iterables
list(pairwise([1, 2, 3, 4])) # [(1, 2), (2, 3), (3, 4)] (3.10+)
list(islice(count(10, 5), 3)) # [10, 15, 20] first 3 of an endless counterPrefix sums — range totals in O(1)
from itertools import accumulate
a = [3, 1, 4, 1, 5]
pre = list(accumulate(a, initial=0)) # [0, 3, 4, 8, 9, 14]
l, r = 1, 3 # sum of a[1..3] inclusive
print(pre[r + 1] - pre[l]) # 6 (1 + 4 + 1)math & functools
import math
math.gcd(12, 18), math.lcm(4, 6) # 6 12
math.isqrt(17) # 4 exact integer square root
math.sqrt(16) # 4.0 (float)
math.ceil(2.1), math.floor(2.9) # 3 2
math.factorial(5) # 120
math.comb(5, 2), math.perm(5, 2) # 10 20 "5 choose 2", ordered picks
math.log2(8), math.log10(1000), math.log(math.e) # 3.0 3.0 1.0
math.pi, math.inf
math.hypot(3, 4) # 5.0 distance
math.prod([2, 3, 4]) # 24
# Ceiling division without floats (safe for huge ints)
a, b = 7, 2
print(-(-a // b)) # 4
print((a + b - 1) // b) # 4
MOD = 10**9 + 7 # the classic contest modulus
print(pow(3, 10**18, MOD)) # fast modular exponentiation
print(pow(3, -1, MOD)) # modular inverse (3.8+)functools — memoisation in one line
import math
from functools import cache, lru_cache, reduce
@cache # Python 3.9+; same as @lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(90)) # instant: 2880067194370816120
@lru_cache(maxsize=None)
def ways(n): # ways to climb n stairs taking 1 or 2 steps
if n <= 1:
return 1
return ways(n - 1) + ways(n - 2)
print(ways(30))
ways.cache_clear() # reset between test cases if needed
print(reduce(lambda acc, x: acc * x, [1, 2, 3, 4])) # 24 fold a list into one value
print(reduce(math.gcd, [12, 18, 24])) # 6 gcd of many numbersBit Manipulation
Numbers are stored as 0s and 1s. Bit tricks let you treat one integer as a row of on/off switches.
DIAGRAM · 13 in binary
8 + 4 + 1 = 13 → 0b1101. Bit i is worth 2ⁱ, counting from the right starting at 0.
a, b = 0b1100, 0b1010 # 12, 10
a & b, a | b, a ^ b # 8 14 6 AND, OR, XOR
~a # -13 flip all bits
1 << 4, 40 >> 2 # 16 10 multiply / divide by powers of 2
x, i = 13, 2
(x >> i) & 1 # 1 is bit i on?
x | (1 << i) # turn bit i on
x & ~(1 << i) # turn bit i off
x ^ (1 << i) # toggle bit i
x & -x # lowest set bit (1)
x & (x - 1) == 0 # power of two? (for x > 0)
bin(x).count("1") # 3 number of 1 bits
x.bit_count() # 3 same, faster (3.10+)
x.bit_length() # 4 bits needed
# Every subset of n items, as bitmasks
items = ["a", "b", "c"]
n = len(items)
for mask in range(1 << n):
subset = [items[j] for j in range(n) if mask >> j & 1]
print(mask, subset)
# XOR trick: find the one number without a pair
from functools import reduce
from operator import xor
print(reduce(xor, [4, 1, 2, 1, 2])) # 4Big Numbers & Precision
Python ints never overflow — they grow as big as you need. Floats, however, are approximations.
print(2 ** 200) # exact, no overflow
print(0.1 + 0.2) # 0.30000000000000004 (!)
print(0.1 + 0.2 == 0.3) # False
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True compare floats safely
print(abs((0.1 + 0.2) - 0.3) < 1e-9) # True manual epsilon
from decimal import Decimal, getcontext
getcontext().prec = 50
print(Decimal("0.1") + Decimal("0.2")) # 0.3 exact decimal maths
print(Decimal(2).sqrt()) # 50 significant digits
from fractions import Fraction
print(Fraction(1, 3) + Fraction(1, 6)) # 1/2 exact fractions
print(10**18 // 7, 10**18 % 7) # stay in integers when you can
print(1_000_000 + 1) # underscores make big literals readable
import sys
sys.set_int_max_str_digits(0) # lift the 4300-digit str(int) limit (3.11+)Classes & Dataclasses
A class is a blueprint for making your own type of object that bundles data and behaviour. In tests you'll mostly use them for nodes (linked lists, trees) and small helper structures.
class Node:
def __init__(self, val, next=None): # runs when you create one
self.val = val
self.next = next
def __repr__(self): # how it prints
return f"Node({self.val})"
head = Node(1, Node(2, Node(3)))
cur = head
while cur:
print(cur.val, end=" ") # 1 2 3
cur = cur.next
print()
from dataclasses import dataclass, field
@dataclass(order=True) # free __init__, __repr__, ==, and <
class Job:
priority: int
name: str = field(compare=False)
jobs = sorted([Job(3, "c"), Job(1, "a")])
print(jobs) # [Job(priority=1, name='a'), Job(priority=3, name='c')]
# Union-Find (Disjoint Set) -- a classic helper class
class DSU:
def __init__(self, n):
self.parent = list(range(n))
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]] # path halving
x = self.parent[x]
return x
def union(self, a, b):
ra, rb = self.find(a), self.find(b)
if ra == rb:
return False
self.parent[ra] = rb
return True
d = DSU(5); d.union(0, 1); d.union(1, 2)
print(d.find(0) == d.find(2)) # TrueErrors & Debugging
Errors aren't failures; they're Python telling you exactly which line confused it. Read the last line of the message first.
| Error | Usually means |
|---|---|
| IndexError | You asked for a[5] but the list is shorter. Check off-by-one loops. |
| KeyError | That key isn't in the dict. Use .get() or defaultdict. |
| ValueError | Right type, wrong value: int("abc"), or unpacking the wrong number of items. |
| TypeError | Mixing types: "a" + 1, or calling something that isn't a function. |
| NameError | Typo in a variable name, or it wasn't defined yet. |
| RecursionError | No base case, or recursion too deep. Raise the limit or use a loop. |
| ZeroDivisionError | Dividing by zero. Check denominators. |
| IndentationError | Mixed tabs/spaces or a missing indent after :. |
try:
value = int("abc")
except ValueError as e:
print("Not a number:", e)
except (KeyError, IndexError):
print("lookup failed")
else:
print("ran only if no error")
finally:
print("always runs (cleanup)")
def withdraw(balance, amount):
if amount > balance:
raise ValueError("insufficient funds") # throw your own error
return balance - amount
assert withdraw(10, 3) == 7, "sanity check failed" # crash loudly if false
# Quick debugging: = in an f-string prints the name and value
x, y = 3, 4
print(f"{x=} {y=} {x * y=}") # x=3 y=4 x * y=12
import sys
print("debug info", file=sys.stderr) # judges ignore stderr: safe for debug printsReading & Writing Files
Your program can save data to disk and load it back later. You open a file in a mode, use it, and close it.
DIAGRAM · The life of a file
| Mode | Meaning | If the file exists… | If it doesn't… |
|---|---|---|---|
| "r" | read (default) | reads it | error |
| "w" | write | erases it first | creates it |
| "a" | append | adds to the end | creates it |
| "x" | exclusive create | error | creates it |
| "r+" | read and write | opens it | error |
| +"b" | binary (images, PDFs): "rb", "wb" | works with bytes instead of text | |
Text files
# Write (creates or overwrites notes.txt)
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("first line\n")
f.write("second line\n")
f.writelines(["third\n", "fourth\n"])
print("fifth", file=f) # print can write to files too
# Append
with open("notes.txt", "a", encoding="utf-8") as f:
f.write("appended line\n")
# Read everything
with open("notes.txt", encoding="utf-8") as f:
text = f.read() # one big string
# Read line by line (memory friendly for huge files)
with open("notes.txt", encoding="utf-8") as f:
for line in f:
print(line.rstrip("\n"))
# Other ways to read
with open("notes.txt", encoding="utf-8") as f:
first = f.readline() # just one line
rest = f.readlines() # list of the remaining lines
lines = open("notes.txt", encoding="utf-8").read().splitlines() # quick, no "\n"s
print(lines)CSV files (spreadsheets)
import csv
rows = [["name", "score"], ["ada", 95], ["alan", 88]]
with open("scores.csv", "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
with open("scores.csv", newline="", encoding="utf-8") as f:
for row in csv.reader(f):
print(row) # ['name', 'score'], ['ada', '95'], ...
with open("scores.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f): # each row becomes a dict by header
print(row["name"], int(row["score"]))
with open("people.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["name", "city"])
w.writeheader()
w.writerow({"name": "grace", "city": "NYC"})JSON files (structured data)
import json
data = {"name": "ada", "skills": ["math", "code"], "active": True}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2) # Python -> file
with open("data.json", encoding="utf-8") as f:
loaded = json.load(f) # file -> Python
print(loaded["skills"][0]) # math
text = json.dumps(data) # Python -> string
back = json.loads(text) # string -> PythonPaths, folders and housekeeping
from pathlib import Path
import os, shutil
p = Path("output") / "report.txt" # builds paths that work on every OS
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("hello\n", encoding="utf-8") # one-liner write
print(p.read_text(encoding="utf-8")) # one-liner read
print(p.exists(), p.name, p.stem, p.suffix) # True report.txt report .txt
for file in Path(".").glob("*.csv"): # every .csv in the current folder
print(file, file.stat().st_size, "bytes")
print(os.getcwd()) # current working directory
print(os.listdir("output")) # ['report.txt']
# Binary copy (works for images, PDFs...)
with open("output/report.txt", "rb") as src, open("output/copy.bin", "wb") as dst:
dst.write(src.read())
# Clean up the demo files
for name in ["notes.txt", "scores.csv", "people.csv", "data.json"]:
os.remove(name)
shutil.rmtree("output") # delete a folder and everything in itWeb Scraping
Scraping means downloading a web page with code and pulling out the parts you care about — prices, headlines, tables — instead of copying them by hand.
DIAGRAM · The scraping pipeline
pip install requests beautifulsoup4Step 1 — understand HTML
Web pages are made of nested tags. Tags have names (h2, a), and attributes like class and href. Right-click any page → Inspect to see them.
<div class="product">
<h2 class="title">Blue Mug</h2>
<span class="price">$12.50</span>
<a href="/mug/blue">details</a>
</div>Step 2 — parse it (works offline)
from bs4 import BeautifulSoup
html = """
<div class="product"><h2 class="title">Blue Mug</h2><span class="price">$12.50</span><a href="/mug/blue">details</a></div>
<div class="product"><h2 class="title">Red Cup</h2><span class="price">$8.00</span><a href="/cup/red">details</a></div>
"""
soup = BeautifulSoup(html, "html.parser")
print(soup.find("h2").text) # first match: Blue Mug
print(len(soup.find_all("div", class_="product"))) # 2
for card in soup.select("div.product"): # CSS selectors
title = card.select_one(".title").get_text(strip=True)
price = float(card.select_one(".price").text.strip("$"))
link = card.find("a")["href"] # read an attribute
print(title, price, link)Step 3 — fetch a real page and save the results
quotes.toscrape.com is a site built specifically for scraping practice.
import csv
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
BASE = "https://quotes.toscrape.com/"
headers = {"User-Agent": "python-101-tutorial (learning project)"}
rows = []
url = BASE
while url:
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status() # stop on 404 / 500 errors
soup = BeautifulSoup(resp.text, "html.parser")
for q in soup.select("div.quote"):
rows.append({
"text": q.select_one("span.text").get_text(strip=True),
"author": q.select_one("small.author").get_text(strip=True),
"tags": ", ".join(t.text for t in q.select("a.tag")),
})
nxt = soup.select_one("li.next a") # follow pagination
url = urljoin(url, nxt["href"]) if nxt else None
time.sleep(1) # be polite: 1 request per second
with open("quotes.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["text", "author", "tags"])
writer.writeheader()
writer.writerows(rows)
print(f"saved {len(rows)} quotes")Useful extras
import requests
# Query parameters and JSON APIs (often easier than scraping HTML!)
r = requests.get("https://api.github.com/search/repositories",
params={"q": "python", "per_page": 3}, timeout=10)
print(r.status_code) # 200 = OK
for repo in r.json()["items"]:
print(repo["full_name"])
# Download a file (e.g. an image) in binary mode
img = requests.get("https://www.python.org/static/img/python-logo.png", timeout=10)
with open("logo.png", "wb") as f:
f.write(img.content)
# HTML tables straight into a DataFrame (pip install pandas lxml)
import pandas as pd
tables = pd.read_html("https://en.wikipedia.org/wiki/List_of_programming_languages_by_type")
print(tables[0].head())
# No third-party libraries allowed? Use the standard library
from urllib.request import urlopen
html = urlopen("https://quotes.toscrape.com/").read().decode("utf-8")
print(html[:100])- Check https://site.com/robots.txt and the site's terms of service. Prefer an official API if one exists.
- Slow down (time.sleep) and identify yourself in the User-Agent. Don't hammer servers.
- Don't scrape personal data or content behind a login without permission.
- Pages built with JavaScript may show nothing to requests. Look for the JSON API in your browser's Network tab, or use a browser tool like Playwright.
Speed Tips & Pitfalls
Python is slower than C++, so a few habits matter when time limits are tight. A rough budget: Python does about 10⁷ simple operations per second.
✅ Do
- Read input with sys.stdin; print once with join.
- Put your code in a main() function: local variables are faster than globals.
- Use sets/dicts for lookups, deque for queues, heapq for “smallest next”.
- Prefer built-ins (sum, sorted, comprehensions) over manual loops.
- Test with the largest allowed input before submitting.
- If available, submit with PyPy: often 5–10× faster for loops.
❌ Avoid
- list.pop(0) or list.insert(0, x) in loops.
- x in big_list inside a loop.
- [[0]*m]*n for grids.
- Mutable default arguments.
- Comparing floats with ==.
- Naming variables list, sum, max, input by accident — you hide the built-in.
Copying correctly
import copy
grid = [[1, 2], [3, 4]]
shallow = grid[:] # new outer list, SAME inner lists
deep = copy.deepcopy(grid) # everything copied
rows_copy = [row[:] for row in grid] # fast deep copy for a 2D list
grid[0][0] = 99
print(shallow[0][0], deep[0][0], rows_copy[0][0]) # 99 1 1Time complexity cheat sheet
| Operation | list | dict / set | deque | heapq |
|---|---|---|---|---|
| Index a[i] | O(1) | — | O(n) middle | min O(1) |
| Add at end | O(1) | O(1) | O(1) | O(log n) push |
| Remove from front | O(n) | — | O(1) | O(log n) pop |
| x in c | O(n) | O(1) | O(n) | O(n) |
| Sort | O(n log n) | — | — | — |
| Input size n | Approach that usually fits in ~1 second (Python) |
|---|---|
| n ≤ 10 | O(n!) — try every permutation |
| n ≤ 20 | O(2ⁿ) — every subset / bitmask |
| n ≤ 500 | O(n³) |
| n ≤ 5,000 | O(n²) |
| n ≤ 10⁶ | O(n log n) or O(n) |
| n ≥ 10⁹ | O(log n) or O(1) — maths / binary search |
Contest Template
Paste this at the start of every solution. It has fast I/O, the common imports, and a main() function ready to fill in.
import sys
from collections import Counter, defaultdict, deque
from heapq import heappush, heappop, heapify
from bisect import bisect_left, bisect_right, insort
from itertools import accumulate, permutations, combinations, product
from functools import cache, lru_cache, reduce, cmp_to_key
from math import gcd, lcm, isqrt, inf, comb
input = sys.stdin.readline
sys.setrecursionlimit(10**6)
MOD = 10**9 + 7
def ints():
return list(map(int, input().split()))
def solve():
n = int(input())
a = ints()
return sum(a) # <-- your algorithm here
def main():
t = int(input()) # remove these two lines if there is only one test
out = [str(solve()) for _ in range(t)]
print("\n".join(out))
if __name__ == "__main__":
main()Try it: save as sol.py, put this in in.txt, then run python3 sol.py < in.txt.
2
3
1 2 3
2
10 20Expected output: 6 and 30 on separate lines.
One-Page Cheat Sheet
Everything above, compressed. Screenshot it, print it, revise it the night before.
I/O
n = int(input())
a = list(map(int, input().split()))
input = sys.stdin.readline
print(*a) · print("\n".join(out))
f"{x:.6f}"
Strings
s[::-1] · s.split() · "".join(l)
ord(c) - ord("a") · chr(97)
s.count(x) · s.find(x) · s.replace(a, b)
s.isdigit() · s.isalpha() · s.lower()
Lists & sorting
[[0]*m for _ in range(n)] sorted(a, key=lambda x: (-x[1], x[0])) enumerate(a) · zip(a, b) · zip(*grid) accumulate(a, initial=0) # prefix sums
Containers
Counter(a).most_common(k) defaultdict(list) · deque().popleft() heappush(h, x) · heappop(h) · -x for max bisect_left(a, x) · insort(a, x)
Maths & bits
gcd lcm isqrt comb factorial pow(b, e, MOD) · pow(x, -1, MOD) (a + b - 1) // b # ceil div x >> i & 1 · x.bit_count() · 1 << n
Files & web
with open(p, "r"|"w"|"a", encoding="utf-8") as f: csv.reader / csv.DictWriter json.load(f) · json.dump(d, f, indent=2) requests.get(url, timeout=10) BeautifulSoup(html, "html.parser").select(css)