Norvig's key move is to treat assigning a digit as an act of elimination - fixing a cell also strikes that digit from its peers' candidates. Today you write that operation on the candidate grid, the atom all propagation is built from.
Assign a digit to a cell in the candidate grid and remove it from every peer's candidates.
Norvig’s insight is that placing a digit and enforcing the rules are the same
action. To assign digit d to a cell, you set that cell’s candidates to just
{d} and, crucially, eliminate d from all 20 of its peers - because none of
them may repeat it. Working on the candidate grid rather than the plain board means
this single step both records the choice and propagates its immediate consequence.
That is the whole atom of constraint propagation. On its own it just does one cell’s bookkeeping, but stacked and cascaded it will solve large parts of a puzzle without any guessing. Keep it non-destructive - operate on a copy - so the search can assign a digit into a trial grid, follow it, and throw the trial away if it fails, exactly as the backtracking solver does with plain placement.
// fix the cell to {d}, and eliminate d from all 20 peers// named AssignCG so it does not clash with the grid Assign from lesson 11func AssignCG(cg [81]Set, cell, d int) [81]Set {cg[cell] = SetOf(d)for _, p := range Peers(cell) { cg[p] = cg[p].Remove(d) }return cg}// work on a copy so search can try an assignment and discard it