Appendix B: Python Setup and Reference
You can read this entire book without running a single line of code. Every example shows its result
inline, in an # Expected output: comment, so the page is self-contained. But discrete math rewards
experimentation: the moment a proof feels slippery, testing the claim on a thousand inputs makes it
concrete, and building the Discrete Math Toolkit (dmtoolkit) one piece per chapter is how the
ideas become yours. This appendix gets Python running on your machine, installs the four libraries
the book uses, and gives you a focused reference for exactly the Python features the examples rely on —
no more. If you already write Python comfortably, skim the "Reading the # Expected output:
convention" and "Running the examples and the Toolkit" sections and skip the rest.
A note on philosophy: throughout the book, libraries like networkx and sympy appear only after
the concept has been built from scratch. You will implement gcd, BFS, and modular exponentiation
yourself before you ever call a library version. The libraries are for checking your work and for
scaling up — not for hiding the ideas.
B.1 Installing Python 3.10+
The book uses Python 3.10 or newer. Three features in particular show up in the code:
structural pattern matching (match/case), the int | None union-type syntax for hints, and
modern f-strings. Anything 3.10+ will do; 3.11 or 3.12 is fine and faster.
Check what you already have. Open a terminal (PowerShell on Windows, Terminal on macOS/Linux) and run:
python --version
If it prints Python 3.10.x or higher, you're done — skip to Section B.2. On some systems the command
is python3:
python3 --version
⚠️ Common Pitfall: On macOS and many Linux distributions, a bare
pythonmay not exist or may point at an ancient Python 2. Usepython3(andpip3) there. On Windows, typingpythonwith no install opens the Microsoft Store — that's a sign Python isn't installed yet, not an error to fight.
If you need to install it:
| Platform | Recommended way |
|---|---|
| Windows | Download the installer from python.org/downloads. Check "Add python.exe to PATH" on the first screen. |
| macOS | python.org installer, or brew install python@3.12 if you use Homebrew. |
| Linux (Debian/Ubuntu) | sudo apt update && sudo apt install python3 python3-venv python3-pip |
| Any platform | The Anaconda or Miniconda distribution bundles Python + the scientific stack. |
After installing, close and reopen your terminal so the updated PATH takes effect, then re-run the
version check. The rest of this appendix writes python and pip; substitute python3/pip3 if
that's what your system uses.
B.2 Creating a virtual environment
A virtual environment is a private, project-local copy of Python's package area. Installing the book's libraries into one keeps them from colliding with other projects or with system Python — the single most common cause of "it worked yesterday" breakage.
💡 Intuition: Think of a virtual environment as a clean desk for one project. Everything you install stays on that desk; when you're done you can sweep the whole desk into the trash (delete the folder) and your system is untouched.
From the folder where you keep the book's code, create and activate an environment named .venv:
python -m venv .venv
Then activate it — the command differs by shell:
| Shell / OS | Activation command |
|---|---|
| Windows PowerShell | .\.venv\Scripts\Activate.ps1 |
| Windows Command Prompt | .\.venv\Scripts\activate.bat |
| macOS / Linux (bash, zsh) | source .venv/bin/activate |
When it's active, your prompt shows a (.venv) prefix. Now python and pip refer to the
environment's copies. To leave it, type deactivate.
⚠️ Common Pitfall: On Windows PowerShell, activation may fail with a message about scripts being disabled. Allow signed local scripts for your user once, then activate again:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned. This is a per-user setting, not a system-wide change.
You only create the environment once. In future sessions you just activate the existing .venv.
B.3 Installing the libraries
The book lists its dependencies in requirements.txt:
networkx>=3.0 # graphs: building, traversal, shortest paths, flow
sympy>=1.12 # symbolic math: primes, modular inverse, gcd, factoring
numpy>=1.24 # arrays, matrix exponentiation (Fibonacci), simulation
matplotlib>=3.7 # plotting (growth rates, distributions, graphs)
With your virtual environment active, install all four at once:
pip install -r requirements.txt
Or, if you don't have the file handy, name them directly:
pip install "networkx>=3.0" "sympy>=1.12" "numpy>=1.24" "matplotlib>=3.7"
Confirm the install with a one-liner. (This does execute code — run it yourself; it is not one of the book's hand-traced examples.)
import networkx, sympy, numpy, matplotlib
print(networkx.__version__, sympy.__version__, numpy.__version__, matplotlib.__version__)
# Example output (your exact versions may differ):
# 3.2.1 1.12 1.26.4 3.8.2
Here is where each library earns its place, mapped to the part of the book that uses it:
| Library | What it does for us | First heavy use |
|---|---|---|
networkx |
Builds graphs and runs BFS, shortest paths, MST, and max-flow — after we've coded them by hand. | Part V (graphs), Chapters 27–34 |
sympy |
Exact integer math: primality, factoring, gcd, modular inverse — perfect for the number-theory and RSA chapters. |
Chapters 22–25 |
numpy |
Fast arrays and matrix exponentiation — how the $O(\log n)$ Fibonacci of Chapter 19 actually runs. | Chapters 19–21 |
matplotlib |
Plots growth rates, probability distributions, and small graphs. | Anywhere a picture helps |
🔗 Connection: The full, assembled
dmtoolkitpackage — every module fromlogic.pytographs.py— is reproduced in Appendix I. This appendix is about getting Python ready to run that code; Appendix I is the code itself.
B.4 A focused Python quick-reference
This covers exactly the Python the book's examples use. It is a reminder, not a tutorial — if you've never programmed before, work through the first few chapters of any introductory Python resource first (see Appendix J / the further-reading lists). Everything below maps directly onto a discrete-math idea you'll meet in the chapters.
Variables, numbers, and printing
Python infers types; you don't declare them. Integers are arbitrary-precision (no overflow — vital for the large numbers in RSA).
n = 17 # int — exact, unbounded
ratio = 1.618 # float
name = "Fibonacci" # str
is_prime = True # bool: True / False (capitalized)
print(n, ratio, name) # space-separated
print(f"{name} hits {n}") # f-string: expressions inside {}
Arithmetic operators you'll use constantly:
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ - * / |
add, subtract, multiply, true divide | 7 / 2 |
3.5 |
// |
floor division (integer quotient) | 7 // 2 |
3 |
% |
modulo (remainder) — the engine of Chapters 22–23 | 7 % 3 |
1 |
** |
exponent | 2 ** 10 |
1024 |
💡 Intuition:
//and%together are the Division Algorithm: foraand positiveb,a == b * (a // b) + (a % b). That single identity underlies gcd, modular arithmetic, and hashing.
Lists
An ordered, mutable sequence — the workhorse for sequences, paths, and rows of a truth table.
xs = [3, 1, 4, 1, 5]
xs[0] # 3 (zero-indexed)
xs[-1] # 5 (last element)
xs[1:3] # [1, 4] (slice: start inclusive, stop exclusive)
len(xs) # 5
xs.append(9) # now [3, 1, 4, 1, 5, 9]
3 in xs # True (membership test)
sum(xs) # 23
sorted(xs) # a new sorted list; xs unchanged
Dictionaries
Key–value maps. They model functions $f\colon A \to B$, adjacency lists for graphs, and memo tables for recursion.
deg = {"A": 2, "B": 3, "C": 1} # vertex -> degree
deg["A"] # 2
deg["D"] = 0 # add / update a key
"B" in deg # True (checks keys)
deg.get("Z", 0) # 0 — a safe default when the key is absent
for v, d in deg.items():
print(v, d) # iterate key/value pairs
🔗 Connection: In Part V we represent a graph $G=(V,E)$ as a dictionary mapping each vertex to the list of its neighbors —
{"A": ["B", "C"], ...}. That adjacency-list dictionary is the data structure behind theGraphclass ingraphs.py.
Sets
An unordered collection of distinct elements — Python's direct model of a mathematical set. Chapter 8 leans on these heavily.
A = {1, 2, 3}
B = {3, 4, 5}
A | B # union -> {1, 2, 3, 4, 5}
A & B # intersection -> {3}
A - B # difference -> {1, 2}
A ^ B # symmetric diff -> {1, 2, 4, 5}
3 in A # membership -> True
A <= B # subset test -> False
len(A) # cardinality -> 3
These operators mirror the notation in Appendix A almost symbol-for-symbol: | is $\cup$, & is
$\cap$, - is $\setminus$, <= is $\subseteq$. The empty set is written set() (not {}, which is
an empty dictionary).
Comprehensions
A comprehension builds a list, set, or dict from an existing iterable in one line. It reads almost exactly like set-builder notation $\{x^2 \mid x \in S,\ x \text{ even}\}$.
squares = [x**2 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]
S = {x % 3 for x in range(10)} # {0, 1, 2} (a set)
sq_map = {x: x**2 for x in range(4)} # {0:0, 1:1, 2:4, 3:9}
range(a, b) yields a, a+1, …, b-1 (stop exclusive); range(n) starts at 0. This off-by-one-friendly
half-open convention is why so much CS counting starts at 0.
Functions
Defined with def; the result comes back via return. You can give parameters default values.
def is_divisible(a, b):
"""True if b divides a evenly."""
return a % b == 0
is_divisible(12, 4) # True
is_divisible(13, 4) # False
Type hints are optional and never required to run; the book uses them lightly for clarity:
def gcd(a: int, b: int) -> int:
while b:
a, b = b, a % b # simultaneous assignment — no temp variable
return a
That a, b = b, a % b is tuple unpacking: the right side is evaluated fully before either
assignment, so the swap is clean. You'll see it in the Euclidean algorithm and in the Fibonacci loop.
Recursion
A function that calls itself, with a base case that stops the descent. This is the computational twin of induction (Chapters 6–7): the base case is the induction basis, the recursive call is the inductive step.
def factorial(n: int) -> int:
if n == 0: # base case
return 1
return n * factorial(n - 1) # recursive case
factorial(5) # 120
⚠️ Common Pitfall: Forget the base case and the function recurses forever, ending in a
RecursionError. Every recursive function in this book names its base case in a comment — get in the habit. And beware naive recursive Fibonacci: it recomputes the same values exponentially, which is exactly the motivation for the fast methods in Chapter 19.
A touch of classes
A class bundles data with the functions that act on it. The book uses classes sparingly — most
prominently the Graph class in graphs.py. You only need to read this much:
class Graph:
def __init__(self): # the constructor; runs on Graph()
self.adj = {} # self holds the object's own data
def add_edge(self, u, v): # a method; self is the instance
self.adj.setdefault(u, []).append(v)
self.adj.setdefault(v, []).append(u)
g = Graph() # create an instance
g.add_edge("A", "B")
g.adj # {'A': ['B'], 'B': ['A']}
__init__ is the constructor, self is the current instance, and methods are just functions whose
first parameter is self. That's the entire object-oriented vocabulary the book assumes.
B.5 Reading the book's # Expected output: convention
Because no code in this book is ever executed by the author — every result is derived by hand — each example carries its answer in a trailing comment so you can verify it without a machine. The convention is simple and consistent:
def comb_count(n, k):
from math import comb
return comb(n, k)
print(comb_count(5, 2))
# Expected output:
# 10
Read it like this:
- Everything below the line
# Expected output:is what the code prints, with each printed line on its own#comment line — not part of the program. If you type only the code above that marker and run it, you should see exactly the lines shown below it. - When output spans several lines (say, a truth table), every output line gets its own
#. The formatting inside the comment mirrors the real output, including spacing. - A single
# =>arrow is occasionally used inline to show the value of one expression, e.g.gcd(48, 36) # => 12. Same idea, compact form.
💡 Intuition: Treat each
# Expected output:block as a tiny test you can run in your head first. Predict the output, then read the comment. When your prediction and the comment disagree, you've found a gap in your understanding — which is the whole point.
This convention is also a promise about correctness. The output was produced by reasoning through the code, the same way you'd hand-trace an algorithm in an exam. If you ever run an example and get a different result, first suspect your environment (wrong Python or library version), then re-read the trace — and if a genuine discrepancy survives, that's a real erratum worth reporting.
B.6 Running the examples and the Toolkit
There are three comfortable ways to run the code, from quickest to most thorough.
1. The interactive interpreter (REPL). Type python with no arguments to get a >>> prompt.
Paste a few lines, see results immediately. Ideal for poking at set operations or checking one
expression. Type exit() or press Ctrl-D (Ctrl-Z then Enter on Windows) to leave.
2. A script file. Save an example as, say, try_it.py, then run it:
python try_it.py
Each chapter's code/ folder holds runnable scripts: example-01-*.py through example-NN-*.py mirror
the in-chapter examples (each ending with its hand-derived # Expected output: comment),
exercise-solutions.py collects the worked exercises, and project-checkpoint.py contains that
chapter's dmtoolkit increment.
3. A Jupyter notebook. If you installed Jupyter (or use the Anaconda distribution), run
jupyter lab and work in cells. Notebooks are the most pleasant way to interleave a plot, a quick
test, and a note to yourself.
Building and using dmtoolkit. The toolkit is a single Python package you grow across the book —
one function per chapter, added in each chapter's Project Checkpoint. The module layout is fixed
(so the pieces compose) and looks like this:
dmtoolkit/
__init__.py
logic.py # Chapters 1–3
sets.py # Chapter 8
relations.py # Chapters 12–13
combinatorics.py # Chapters 15–17
recurrences.py # Chapters 18–19
probability.py # Chapters 20–21
numbertheory.py # Chapters 22–23
crypto.py # Chapters 24–25
graphs.py # Chapters 27–34
coding.py # Chapters 26, 38
Once a module exists, import from it like any package:
from dmtoolkit.numbertheory import gcd, mod_pow
gcd(48, 36) # => 12
mod_pow(7, 256, 13) # 7^256 mod 13, computed fast
🔗 Connection: Don't worry about assembling the whole package as you read — the complete, reconciled
dmtoolkitsource is printed in Appendix I. The Project Checkpoints walk you through writing each piece; Appendix I is the finished reference if you'd rather read or copy the assembled version.
A final reassurance: nothing here is a prerequisite for understanding the mathematics. The proofs stand on their own, and every output you need is already on the page. The code is an invitation — a way to turn "I followed the proof" into "I can build the thing the proof is about." When you're ready to accept that invitation, this appendix is your launch pad. Now go run something.