Skip to content

MartinBuchmann/cl-str8ts

Repository files navigation

CL-STR8TS

Introduction

I came across str8ts puzzles recently and want to implement have implemented a solver in Common Lisp similar to my binoxxo solver. I am using basic ideas from Peter Norvig’s Soduko solver as implemented here or here in Common Lisp.

Encoding the puzzle

While in a Sudoku puzzle the units are always the same for every puzzle, in str8ts the units have to be found for each puzzle due to varying the blocked fields and digits.

I have chosen to encode a blocked field with 10 and a digit within a blocked field with a negative integer. A puzzle is read from a text file with one puzzle per line given a integers from -9 to 10 separated by white space.

The algorithm

I am sure there are certain things which could be improved with respect to performance. I have chosen to store the puzzle itself in a two-dimensional array allowing to access the fields in a straight-forward way. The calculation of the units and sub-units is not optimized for space or speed. I found the usage of lists of lists pretty straight-forward during implementation and am pretty happy with it.

After reading the puzzle from the file for each field the impossible digits are eliminated using assign and eliminate. Then for the field with the least possible digits the next step is generated and further checked using search-puzzle and valid-puzzle-p until solvedp signals that the puzzle is solved.

Loading and running

Load and test the program with:

(asdf:make :str8ts)
(asdf:test-system :str8ts)

See the directory puzzles for the given puzzles or add more yourself.

To solve a puzzle use solve-puzzle:

(solve-puzzle #p"puzzles/2019-01-29-hard")

Initial puzzle:
 -----------------------------------------------------
|  10 |   0 |   0 |  -8 |   4 |   0 |   0 |   0 |  10 |
|  10 |   0 |   0 |  -1 |   0 |   0 |  10 |   0 |   9 |
|   0 |   0 |   2 |  10 |   0 |   0 |  -7 |   0 |   0 |
|   0 |   0 |  10 |   0 |   0 |  10 |   0 |   0 |  10 |
|  10 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |  10 |
|  10 |   0 |   0 |  10 |   0 |   0 |  -9 |   0 |   2 |
|   7 |   0 |  -4 |   0 |   0 |  10 |   0 |   1 |   0 |
|   0 |   0 |  -5 |   0 |   0 |  10 |   0 |   0 |  -6 |
|  10 |   0 |   0 |   0 |   0 |  -2 |   0 |   0 |  10 |
 -----------------------------------------------------
Final state:
 -----------------------------------------------------
|  10 |   2 |   1 |  -8 |   4 |   5 |   6 |   7 |  10 |
|  10 |   4 |   3 |  -1 |   7 |   6 |  10 |   8 |   9 |
|   3 |   1 |   2 |  10 |   5 |   4 |  -7 |   9 |   8 |
|   4 |   3 |  10 |   2 |   1 |  10 |   5 |   6 |  10 |
|  10 |   6 |   7 |   3 |   2 |   8 |   4 |   5 |  10 |
|  10 |   5 |   6 |  10 |   8 |   7 |  -9 |   3 |   2 |
|   7 |   8 |  -4 |   5 |   6 |  10 |   2 |   1 |   3 |
|   8 |   9 |  -5 |   4 |   3 |  10 |   1 |   2 |  -6 |
|  10 |   7 |   8 |   6 |   9 |  -2 |   3 |   4 |  10 |
 -----------------------------------------------------
Puzzle solved in 0.005 seconds.

Drawing puzzles

Thanks to the wonderful vecto package the puzzles can also be drawn instead of just print them to the REPL.

(draw-puzzle (make-puzzle) #p"puzzle.png")
(draw-puzzle (search-puzzle (make-puzzle) 0) "solved.png")

Or just:

(solve-puzzle "puzzles/2019-03-05-hard" :draw t)

Initial puzzle

./images/2019-03-05-hard.png

Solved puzzle

./images/2019-03-05-hard-solved.png

Optimization

Although I claimed before that I am happy with my solution, the performance is quite poor, s. below. Obviously, generating every puzzle and then reject it because of in-valid sub-units makes not sense. I have to change eliminate to take care of the checking for valid candidates.

STR8TS> (solve-puzzle "puzzles/2019-03-05-hard")
Initial puzzle:
 -----------------------------------------------------
|   3 |   0 |  10 |  10 |   9 |   0 |   0 |  10 |  -4 |
|   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |
|  10 |  10 |   0 |   0 |   8 |  -5 |  10 |   0 |   0 |
|   0 |  10 |  10 |   0 |   0 |  10 |   0 |   0 |  -1 |
|   0 |   0 |   0 |   0 |   0 |   1 |   0 |   0 |   0 |
|  -7 |   0 |   6 |  -3 |   0 |   0 |  10 |  10 |   0 |
|   0 |   0 |  10 |  10 |   0 |   0 |   0 |  -9 |  10 |
|   0 |   0 |   3 |   0 |   0 |   0 |   0 |   0 |   0 |
|  10 |  10 |   0 |   0 |   0 |  10 |  -8 |   5 |   0 |
 -----------------------------------------------------
Final state:
 -----------------------------------------------------
|   3 |   2 |  10 |  10 |   9 |   8 |   7 |  10 |  -4 |
|   2 |   1 |   8 |   9 |   5 |   7 |   6 |   4 |   3 |
|  10 |  10 |   7 |   6 |   8 |  -5 |  10 |   1 |   2 |
|   5 |  10 |  10 |   7 |   6 |  10 |   4 |   3 |  -1 |
|   4 |   6 |   5 |   8 |   7 |   1 |   3 |   2 |   9 |
|  -7 |   5 |   6 |  -3 |   1 |   2 |  10 |  10 |   8 |
|   8 |   7 |  10 |  10 |   4 |   3 |   2 |  -9 |  10 |
|   9 |   8 |   3 |   5 |   2 |   4 |   1 |   6 |   7 |
|  10 |  10 |   2 |   4 |   3 |  10 |  -8 |   5 |   6 |
 -----------------------------------------------------
Puzzle solved in 13.034 seconds.
T
measuring PROFILE overhead..done
  seconds  |     gc     |    consed   |    calls   |  sec/call  |  name  
--------------------------------------------------------------
     0.690 |      0.020 | 405,385,632 |     34,094 |   0.000020 | VALID-PUZZLE-P
     0.427 |      0.049 |  16,511,376 |    220,724 |   0.000002 | LIST-PLACES-WITH-SINGLE-UNIT-SOLUTION
     0.420 |      0.003 |  35,634,048 |          2 |   0.210000 | DRAW-PUZZLE
     0.256 |      0.007 |  76,331,952 |  2,411,095 |   0.000000 | VALID-SUBUNIT-P
     0.177 |      0.004 |  33,642,896 |     14,527 |   0.000012 | FIND-FIELD-WITH-FEWEST-POSSIBILITIES
     0.146 |      0.000 |  16,354,864 |     68,186 |   0.000002 | COPY-PUZZLE-ARRAY
     0.022 |      0.000 |   5,963,776 |     42,585 |   0.000001 | FIRST-SET-VALUE
     0.021 |      0.000 |           0 |    220,724 |   0.000000 | UNSET-POSSIBLE-VALUE
     0.009 |      0.000 |           0 |     14,529 |   0.000001 | SOLVEDP
     0.004 |      0.000 |   1,539,824 |     34,093 |   0.000000 | COPY-PUZZLE
     0.004 |      0.000 |     819,200 |     14,527 |   0.000000 | LIST-ALL-POSSIBLE-DIGITS
     0.001 |      0.000 |           0 |          1 |   0.000776 | PUZZLE-SUB-UNITS
     0.000 |      0.000 |      32,768 |  2,018,680 |   0.000000 | ELIMINATE
     0.000 |      0.000 |      98,304 |        121 |   0.000000 | LIST-UNITS-CONTAINING
     0.000 |      0.000 |           0 |          1 |   0.000000 | MAKE-PUZZLE
     0.000 |      0.000 |           0 |     42,592 |   0.000000 | ELIMINATE-VALUE-IN-UNITS
     0.000 |      0.000 |      36,144 |          1 |   0.000000 | READ-GRID
     0.000 |      0.000 |           0 |         57 |   0.000000 | SPLIT-SUB-ROWS
     0.000 |      0.000 |           0 |        226 |   0.000000 | IN-SUB-UNIT-P
     0.000 |      0.000 |           0 |  2,018,680 |   0.000000 | VALUE-IS-SET-P
     0.000 |      0.000 |           0 |  1,397,411 |   0.000000 | COUNT-REMAINING-POSSIBLE-DIGITS
     0.000 |      0.000 |      65,504 |          2 |   0.000000 | PRINT-PUZZLE
     0.000 |      0.000 |           0 |         57 |   0.000000 | FIND-SUB-UNITS
     0.000 |      0.000 |           0 |          1 |   0.000000 | SOLVE-PUZZLE
     0.000 |      0.000 |           0 |          1 |   0.000000 | PUZZLE-UNITS
     0.000 |      0.000 |           0 |     65,283 |   0.000000 | ASSIGN
     0.000 |      0.000 |           0 |     34,094 |   0.000000 | SEARCH-PUZZLE
     0.000 |      0.000 |           0 |  2,983,534 |   0.000000 | ONLY-POSSIBLE-VALUE-IS-P
     0.000 |      0.000 |           0 |         57 |   0.000000 | SPLIT-SUB-COLS
--------------------------------------------------------------
     2.176 |      0.083 | 592,416,288 | 11,635,885 |            | Total

estimated total profiling overhead: 10.64 seconds
overhead estimation parameters:
  6.0e-9s/call, 9.14e-7s total profiling, 4.22e-7s internal profiling

After some optimization and implementing only-valid-digits to reduce the search space to valid possibilities only, I gained some speed:

STR8TS> (solve-puzzle "/Users/Martin/Documents/src/lisp/str8ts/puzzles/2019-03-05-hard")
Initial puzzle:
 -----------------------------------------------------
|   3 |   2 |  10 |  10 |   9 |   8 |   7 |  10 |  -4 |
|   0 |   0 |   0 |   0 |   0 |   0 |   6 |   0 |   0 |
|  10 |  10 |   0 |   0 |   8 |  -5 |  10 |   0 |   0 |
|   0 |  10 |  10 |   0 |   0 |  10 |   0 |   0 |  -1 |
|   0 |   0 |   0 |   0 |   0 |   1 |   0 |   0 |   0 |
|  -7 |   5 |   6 |  -3 |   0 |   0 |  10 |  10 |   0 |
|   0 |   0 |  10 |  10 |   0 |   0 |   0 |  -9 |  10 |
|   0 |   0 |   3 |   0 |   0 |   0 |   0 |   0 |   0 |
|  10 |  10 |   0 |   0 |   0 |  10 |  -8 |   5 |   6 |
 -----------------------------------------------------
Final state:
 -----------------------------------------------------
|   3 |   2 |  10 |  10 |   9 |   8 |   7 |  10 |  -4 |
|   2 |   1 |   8 |   9 |   5 |   7 |   6 |   4 |   3 |
|  10 |  10 |   7 |   6 |   8 |  -5 |  10 |   1 |   2 |
|   5 |  10 |  10 |   7 |   6 |  10 |   4 |   3 |  -1 |
|   4 |   6 |   5 |   8 |   7 |   1 |   3 |   2 |   9 |
|  -7 |   5 |   6 |  -3 |   1 |   2 |  10 |  10 |   8 |
|   8 |   7 |  10 |  10 |   4 |   3 |   2 |  -9 |  10 |
|   9 |   8 |   3 |   5 |   2 |   4 |   1 |   6 |   7 |
|  10 |  10 |   2 |   4 |   3 |  10 |  -8 |   5 |   6 |
 -----------------------------------------------------
Puzzle solved in 0.082 seconds.
T
  seconds  |     gc     |   consed   |  calls |  sec/call  |  name  
---------------------------------------------------------
     0.408 |      0.005 | 35,691,440 |      2 |   0.204000 | DRAW-PUZZLE
     0.005 |      0.000 |          0 |  8,076 |   0.000001 | COUNT-REMAINING-POSSIBLE-DIGITS
     0.003 |      0.000 |    131,072 |     84 |   0.000039 | FIND-FIELD-WITH-FEWEST-POSSIBILITIES
     0.002 |      0.000 |    688,128 |  1,272 |   0.000002 | ONLY-VALID-DIGITS
     0.002 |      0.000 |    229,376 |  2,628 |   0.000001 | LIST-ALL-POSSIBLE-DIGITS
     0.002 |      0.000 |     32,768 |    457 |   0.000004 | FIRST-SET-VALUE
     0.002 |      0.000 |          0 | 12,508 |   0.000000 | ONLY-POSSIBLE-VALUE-IS-P
     0.001 |      0.000 |    131,056 |      2 |   0.000500 | PRINT-PUZZLE
     0.001 |      0.000 |     65,376 |    316 |   0.000003 | COPY-PUZZLE-ARRAY
     0.001 |      0.000 |          0 |  1,272 |   0.000000 | UNSET-POSSIBLE-VALUE
     0.000 |      0.000 |     65,536 |  1,045 |   0.000000 | LIST-PLACES-WITH-SINGLE-UNIT-SOLUTION
     0.000 |      0.000 |    262,064 |  7,246 |   0.000000 | VALID-SUBUNIT-P
     0.000 |      0.000 |          0 |     57 |   0.000000 | SPLIT-SUB-COLS
     0.000 |      0.000 |          0 |      1 |   0.000000 | PUZZLE-UNITS
     0.000 |      0.000 |          0 | 25,064 |   0.000000 | VALUE-IS-SET-P
     0.000 |      0.000 |          0 |     57 |   0.000000 | FIND-SUB-UNITS
     0.000 |      0.000 |          0 |     85 |   0.000000 | SEARCH-PUZZLE
     0.000 |      0.000 |     98,304 |    226 |   0.000000 | IN-SUB-UNIT-P
     0.000 |      0.000 |          0 |    464 |   0.000000 | ELIMINATE-VALUE-IN-UNITS
     0.000 |      0.000 |          0 |  1,272 |   0.000000 | INTEGERS->DIGITS
     0.000 |      0.000 |          0 |  1,095 |   0.000000 | ASSIGN
     0.000 |      0.000 |          0 |      1 |   0.000000 | VALID-PUZZLE-P
     0.000 |      0.000 |          0 |      1 |   0.000000 | SOLVE-PUZZLE
     0.000 |      0.000 |          0 |      1 |   0.000000 | READ-GRID
     0.000 |      0.000 |     32,768 |    158 |   0.000000 | COPY-PUZZLE
     0.000 |      0.000 |          0 |     57 |   0.000000 | SPLIT-SUB-ROWS
     0.000 |      0.000 |          0 |      1 |   0.000000 | MAKE-PUZZLE
     0.000 |      0.000 |          0 | 25,064 |   0.000000 | ELIMINATE
     0.000 |      0.000 |          0 |      1 |   0.000000 | PUZZLE-SUB-UNITS
     0.000 |      0.000 |          0 |     86 |   0.000000 | SOLVEDP
     0.000 |      0.000 |     65,536 |    121 |   0.000000 | LIST-UNITS-CONTAINING
---------------------------------------------------------
     0.427 |      0.005 | 37,493,424 | 88,720 |            | Total

estimated total profiling overhead: 0.08 seconds
overhead estimation parameters:
  4.0000003e-9s/call, 8.8599995e-7s total profiling, 3.48e-7s internal profiling

After finding the final(?) bugs this is pretty impressive for my standards. I had to make sure that the *units* were calculated correctly and the digits were set to reasonable values, then I do not have to check each puzzle for validity and save quite some time. I guess I really will leave it as it is this time…

Acknowledgment

I found great help at Stackoverflow for some detail problems which is gratefully acknowledged.

About

A solver for str8ts puzzles in Common Lisp

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published