A blank cell can hold any digit its peers have not already used. Today you compute that candidate set for a single cell, the primitive both the solver and propagation are built from.
Compute the digits a cell could legally hold, given its peers' values.
The most basic deduction in Sudoku is: a blank cell can hold any digit that none of its 20 peers already uses. Starting from the full set of 1 through 9 and removing each peer’s value leaves exactly the candidates for that cell. This one function is the engine of everything that follows - the solver will try a cell’s candidates, and propagation will watch these sets shrink.
Treat a filled cell as a cell whose candidate set is just its own value; that makes
the whole grid uniform - every cell has a candidate set, of size 1 if it is already
decided. Notice the edge the classic puzzle hands you: cell 41’s peers already use
eight distinct digits, so only {4} remains. A blank with a single candidate is a
forced cell, and spotting those is the first thing the solver will exploit.
// blank: Full minus every digit a peer already uses; filled: just its valuefunc Candidates(g [81]int, cell int) Set {if g[cell] != 0 { return singleton(g[cell]) }s := Fullfor _, p := range Peers(cell) {if g[p] != 0 { s = s.Remove(g[p]) }}return s}