A primary goal here for reasonable performance was to minimize (within reasonable coding effort) the number of continuations which have to be captured by the amb operator---hence the repeated narrowing of possibilities before calls to amb. Here's the code (including a nifty macro which allows specifying a puzzle easily):
(module sudoku mzscheme
(require (all-except (lib "43.ss" "srfi") vector-fill! vector->list)
(only (lib "1.ss" "srfi") filter take drop)
(lib "extra.ss" "swindle"))
(provide sudoku-board solve write-sudoku-board)
;; Sudoku board is eventually represented as a vector of rows
;; which are themselves vectors of length nine. The syntax
;; sudoku-board produces such a board from a list of numbers or
;; '?, which represents an unknown square.
(define (any) (list 1 2 3 4 5 6 7 8 9))
(define (list->board lst)
(apply vector
(let loop ((lst lst) (list-of-nines '()))
(if (null? lst)
(reverse list-of-nines)
(loop (drop lst 9)
(cons (apply vector (take lst 9)) list-of-nines))))))
(define-syntax sudoku-board
(syntax-rules ()
((sudoku-board elt ...)
(list->board
(process-elts (processed ) elt ...)))))
(define-syntax process-elts
(syntax-rules (processed ?)
((process-elts (processed pelt ...) ?)
(reverse (list (any) pelt ...)))
((process-elts (processed pelt ...) elt)
(reverse (list elt pelt ...)))
((process-elts (processed pelt ...) ? elt ...)
(process-elts (processed (any) pelt ...) elt ...))
((process-elts (processed pelt ...) elt elt2 ...)
(process-elts (processed elt pelt ...) elt2 ...))))
(define (cubant i)
(floor (/ i 3)))
(define (same-cubant? ii jj i j)
(and (= (cubant i)
(cubant ii))
(= (cubant j)
(cubant jj))))
(define (known? elt)
(number? elt))
(define (unknown? elt)
(list? elt))
(define (board-map fn board)
(vector-map
(lambda (i row)
(vector-map
(lambda (j elt)
(fn i j elt))
row))
board))
(define (board-fold fn start board)
(vector-fold
(lambda (i start row)
(vector-fold
(lambda (j start elt)
(fn i j start elt))
start
row))
start
board))
(define (board-ref b i j)
(vector-ref (vector-ref b i) j))
(define (prune ii jj number board)
(board-map
(lambda (i j elt)
(if (known? elt)
elt
(cond
((= i ii)
(filter (lambda (elt) (not (= elt number))) elt))
((= j jj)
(filter (lambda (elt) (not (= elt number))) elt))
((same-cubant? ii jj i j)
(filter (lambda (elt) (not (= elt number))) elt))
(else elt))))
board))
(define (singleton? elt)
(and (pair? elt)
(null? (cdr elt))))
(define (expand-singletons board)
(board-map
(lambda (i j elt)
(if (singleton? elt)
(car elt)
elt))
board))
(define (prune-all board)
(let ((new-board
(expand-singletons
(board-fold
(lambda (i j nb elt)
(if (known? elt)
(prune i j elt nb)
nb))
board
board))))
(if (equal? new-board board)
new-board
(prune-all new-board))))
(define (amb-list list)
(if (null? list)
(amb)
(amb (car list) (amb-list (cdr list)))))
(define (amb-board board)
(board-fold
(lambda (i j nb elt)
(let ((elt (board-ref nb i j)))
(if (known? elt)
nb
(let ((choice (amb-list elt)))
(prune-all (board-map
(lambda (ii jj elt) (if (and (= i ii)
(= j jj))
choice
elt))
nb))))))
board
board))
(define (board-assertions board)
(board-fold
(lambda (i j dummy elt1)
(board-fold
(lambda (ii jj dummy elt2)
(cond
((and (= ii i)
(= jj j)))
((= ii i)
(amb-assert (not (= elt1 elt2))))
((= jj j)
(amb-assert (not (= elt1 elt2))))
((same-cubant? ii jj i j)
(amb-assert (not (= elt1 elt2))))))
'()
board))
'()
board))
(define (solve board)
(let ((new-board (prune-all board)))
(let ((final-board (amb-board new-board)))
(board-assertions final-board)
final-board)))
(define (write-sudoku-board board)
(printf "(sudoku-board")
(board-fold
(lambda (i j dummy elt)
(if (= j 0)
(newline))
(if (pair? elt)
(printf " ?")
(printf " ~a" elt))
(if (and (= i 8)
(= j 8))
(printf ")~%")))
'()
board)))
Example of use:
(write-sudoku-board
(solve
(sudoku-board
? ? 8 ? ? ? 1 5 ?
? ? ? ? ? 1 8 ? ?
3 ? 5 4 ? ? ? ? 9
5 ? ? ? ? 9 ? ? ?
? 9 ? 2 3 4 ? 7 ?
? ? ? 1 ? ? ? ? 8
4 ? ? ? ? 5 9 ? 1
? ? 6 7 ? ? ? ? ?
? 5 3 ? ? ? 2 ? ?)))
which evaluates to
(sudoku-board 7 4 8 3 9 2 1 5 6 2 6 9 5 7 1 8 4 3 3 1 5 4 8 6 7 2 9 5 7 4 8 6 9 3 1 2 8 9 1 2 3 4 6 7 5 6 3 2 1 5 7 4 9 8 4 8 7 6 2 5 9 3 1 9 2 6 7 1 3 5 8 4 1 5 3 9 4 8 2 6 7)