The value in a cell is constrained only by the other cells it shares a unit with - its peers. Today you compute, for each cell, the exactly 20 peers it must differ from, the lookup that makes candidates and propagation fast.
Compute the set of peers - the cells sharing a row, column, or box - for each cell.
A cell does not have to differ from all 80 others, only from the ones it shares a constraint with. Those are its peers: the union of its row, its column, and its box, with the cell itself removed. Counting them is a nice check on your units - a row contributes 8 others, the column another 8, and the box adds 4 more that were not already in that row or column, for a total of exactly 20 peers for every cell on the board.
Peers are the workhorse lookup of the solver. “What digits can this blank hold?” is “which digits are not already used by its 20 peers?”, and Norvig’s propagation is “when a cell is fixed, remove that digit from its 20 peers.” Precomputing this list per cell now means those questions never have to rescan the units again.
// peers of a cell = union of the units it belongs to, excluding itselffunc Peers(cell int) []int {seen := map[int]bool{}for _, u := range Units() {if contains(u, cell) {for _, c := range u { if c != cell { seen[c] = true } }}}// return the sorted keys of seen (there will be 20)}