Fork me on GitHub

Project Notes

#390 Solving Sudoku with Prolog

Using Prolog to solve sudoku-style problems

Notes

This is an exercise from Seven Languages in Seven Weeks.

A 4x4 Solver

To get a handle on the challenge, starting with a 4x4 sudoku solver.

See sudoku4x4.pl for the full source. Here it is with annotated explanation:

1. Validation Rules

Ensures that each list in a list of lists contains all different numbers

  • Using the valid/1 predicate
  • The base case: an empty list is trivially valid.
  • If the first element (Head) of the list of lists satisfies fd_all_different/1 (all elements in it are different),
  • then recursively check the rest of the lists (Tail).
valid([]).
valid([Head|Tail]) :- fd_all_different(Head), valid(Tail).

2. The main sudoku/2 predicate

Allow the user to pass in a partially filled puzzle (with some known numbers) and get the Solution back filled in.

  • Puzzle and Solution are unified:
sudoku(Puzzle, Solution) :-
  Solution = Puzzle,

The 16 Sudoku variables

These represent the 4×4 Sudoku grid:

  Puzzle = [S11, S12, S13, S14,
            S21, S22, S23, S24,
            S31, S32, S33, S34,
            S41, S42, S43, S44],

Set the domain of possible values

Each Sudoku cell (Sij) can take any integer from 1 to 4:

  fd_domain(Solution, 1, 4),

Define rows

Each row is defined as a list of four variables:

  Row1 = [S11, S12, S13, S14],
  Row2 = [S21, S22, S23, S24],
  Row3 = [S31, S32, S33, S34],
  Row4 = [S41, S42, S43, S44],

Define columns

Each column collects one cell from each row:

  Col1 = [S11, S21, S31, S41],
  Col2 = [S12, S22, S32, S42],
  Col3 = [S13, S23, S33, S43],
  Col4 = [S14, S24, S34, S44],

Define 2×2 squares (subgrids)

Defines each smaller 2×2 block:

  Square1 = [S11, S12, S21, S22],
  Square2 = [S13, S14, S23, S24],
  Square3 = [S31, S32, S41, S42],
  Square4 = [S33, S34, S43, S44],

Apply the constraints

Uses the valid/1 predicate to enforce that all rows, columns, and squares have unique values (no duplicates).

Note: The actual solving (searching for values) happens implicitly when Prolog tries to satisfy all constraints.

  valid([Row1, Row2, Row3, Row4,
         Col1, Col2, Col3, Col4,
         Square1, Square2, Square3, Square4]).

3. Printing the Sudoku

Entry point for printing

  • Verifies List has 16 elements (a 4×4 grid).
  • Then calls print_rows/2 to display it row by row.
print_sudoku(List) :-
    length(List, 16),
    print_rows(List, 0).

Recursive printer

Base case: stop when no more cells to print:

print_rows([], _).

Print the list, element by element:

  • Prints each number (Head).
  • After every 4th element (Count mod 4 =:= 3), prints a newline (nl), otherwise adds a few spaces (tab(3)).
  • Recursively prints the rest of the list.
print_rows([Head|Tail], Count) :-
    write('    '),
    write(Head),
    (Count mod 4 =:= 3 -> nl ; tab(3)),
    NewCount is Count + 1,
    print_rows(Tail, NewCount).

4. Combine everything

Solves the Sudoku and then prints the result:

solve_and_print(Sudoku) :-
  sudoku(Sudoku, Solution),
  print_sudoku(Solution).

Checking Results

$ gprolog --consult-file sudoku4x4.pl
GNU Prolog 1.5.0 (64 bits)
Compiled Jul  8 2021, 09:35:47 with clang
Copyright (C) 1999-2024 Daniel Diaz

compiling ./sudoku4x4.pl for byte code...
./sudoku4x4.pl compiled, 45 lines read - 6727 bytes written, 3 ms

Running some tests:

| ?- solve_and_print([_, _, 2, 3,
                      _, _, _, _,
                      _, _, _, _,
                      3, 4, _, _]).
    4       1       2       3
    2       3       4       1
    1       2       3       4
    3       4       1       2

yes
| ?- solve_and_print([1, _, 3, _,
                      _, _, _, _,
                      _, _, _, _,
                      4, 2, _, _]).
    1       4       3       2
    2       3       4       1
    3       1       2       4
    4       2       1       3

yes

NB: can also run directly from the command line:

$ gprolog --consult-file sudoku4x4.pl --query-goal "solve_and_print([_, _, 2, 3, _, _, _, _, _, _, _, _, 1, 2, _, _]), nl, halt"
GNU Prolog 1.5.0 (64 bits)
Compiled Jul  8 2021, 09:35:47 with clang
Copyright (C) 1999-2024 Daniel Diaz

compiling /Users/paulgallagher/MyGithub/tardate/LittleCodingKata/prolog/sudoku/sudoku4x4.pl for byte code...
/Users/paulgallagher/MyGithub/tardate/LittleCodingKata/prolog/sudoku/sudoku4x4.pl compiled, 45 lines read - 6727 bytes written, 4 ms
| ?- solve_and_print([_, _, 2, 3, _, _, _, _, _, _, _, _, 1, 2, _, _]), nl, halt.
    4       1       2       3
    2       3       4       1
    3       4       1       2
    1       2       3       4

Extending to a 9x9 Solver

See sudoku9x9.pl - constructed the same way as 4x4, just bigger!

Testing a solve:

$ gprolog --consult-file sudoku9x9.pl
GNU Prolog 1.5.0 (64 bits)
Compiled Jul  8 2021, 09:35:47 with clang
Copyright (C) 1999-2024 Daniel Diaz

compiling ./sudoku9x9.pl for byte code...
./sudoku9x9.pl compiled, 67 lines read - 21234 bytes written, 6 ms

| ?- solve_and_print([9, _, 5, _, 4, 7, 2, 1, 6,
2, _, 7, _, 3, 6, 8, _, 9,
8, 6, _, 2, 1, 9, 5, 7, _,
1, 9, _, 4, 7, 3, _, 5, 2,
_, 4, 6, _, 2, 5, _, 3, 8,
_, _, 3, 1, _, 8, 4, _, 7,
_, 5, 2, 7, 8, 4, 9, 6, _,
6, 8, 9, 3, 5, 1, 7, 2, 4,
4, 7, 1, 6, 9, 2, 3, 8, _]).
    9       3       5       8       4       7       2       1       6
    2       1       7       5       3       6       8       4       9
    8       6       4       2       1       9       5       7       3
    1       9       8       4       7       3       6       5       2
    7       4       6       9       2       5       1       3       8
    5       2       3       1       6       8       4       9       7
    3       5       2       7       8       4       9       6       1
    6       8       9       3       5       1       7       2       4
    4       7       1       6       9       2       3       8       5

yes

Credits and References

About LCK#390 Prolog

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader