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 python may not exist or may point at an ancient Python 2. Use python3 (and pip3) there. On Windows, typing python with 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 dmtoolkit package — every module from logic.py to graphs.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: for a and positive b, 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 the Graph class in graphs.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 dmtoolkit source 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.