Sudoku with handicap
code-golf code-challenge
Note: I've completely reworked this, as the comments convinced me that there's not a good way to describe the restrictions I originally was after in a language-independent way without unreasonably restricting languages. Thanks to all the commenters.
I now reworked the question in a way that also inhibits traditional recursive solving (at least doing so in a straightforward way), and at the same time even allows to add a metrics about the "efficiency" of the algorithm. The basic idea being that your program is called not once, but many times, each time only having limited information about the field.
Also note that this new version requires me to write a driver program; so the question cannot go live until the driver program is written.
Questions are set in italics inside the text
The goal of this challenge is to solve a given Sudoku. However there's a twist: The program cannot access the full board at any time. Instead it is called repeatedly, and each time it has only limited information about the board. I'll refer to the totality of all calls as the "calling loop". The program can then request different information for the next run, or declare that it is finished (that is, request to not be called again; the call loop is terminated).
The only way to pass information between different runs is through the Sudoku board, and a small amount of scratch space. The Sudoku board is initialized before the first call with the Sudoku to solve (obviously) and is then checked after the call loop terminated. During the call loop, the Sudoku board is not checked, so you may "abuse" it to store additional information, as long as at the end, a valid result is generated.
Since it may not be possible to completely solve all Sudokus using such an algorithm, the only hard requirement is that the call loop is guaranteed to eventually terminate, the Sudoku field after termination is in a valid state. The rest is covered by scoring.
Standard loopholes are explicitly disallowed.
The stored data
The data that is stored outside the program consists of 90 nine-it unsigned numerical values (that is, minimal nmumber 0, maximal number 511), 81 of which represent the Sudoku field, and 9 values are scratch space. The values of the field are interpreted as bit fields, as described below.
In the following I'll use as example the Sudoku field
4.5|.7.|89.
..2|.5.|6..
..7|9..|542
---+---+---
..3|5.6|489
...|3.8|...
684|7.9|1..
---+---+---
238|..5|9..
..6|.9.|3..
.79|.3.|2.1
where dots contain fields that have not been filled.
Initially, the data gets filled as follows:
Each field pre-filled with number $n$ is represented by the value $2^{n-1}$, that is, the bit corresponding to that number is set, and all other bits are unset.
The unfilled fields are represented by the value $511$ (that is, all nine bits are set).
The scratch space is filled with $0$.
After the run loop terminates, each pre-filled field needs to have the same value as initially, and each initially empty field must have at least the bit corresponding to the correct solution set. That is, every zero bit represents a value that your program excluded for that field, and a program that excludes the correct solution is disqualified.
The contents of the field is only evaluated at the end of the call loop. So in between your program is free to make creative use of the storage space given.
The input
The program receives its data through standard input of the following form:
The first line contains a description of which data is given to/set by the program in this run. It consists of one to three space-separated words from the following list. On the first run, it is just "S". At later runs, it is exactly what the program requested at its previous run.
The possible values and corresponding interpretation are:
R1
to R9
: The indicated row of the Sudoku, 1
being the uppermost row.
C1
to C9
: The indicated column of the Sudoku, 1
being the leftmost column.
F1
to F9
: The indicated $3\times 3$ subfield of the Sudoku, numbered left to right, up to down. So for example 1
denotes the upper left subfield, 6
denotes the middle right subfield.
S
: The scratch space.
The next one to three lines contain the corresponding data, from left to right, and from up to down, as space separated decimal numbers.
So at the first run, your program will receive the input
S
0 0 0 0 0 0 0 0 0
At the second run with the example Sudoku field, the input to your program might be:
R2 C3 F4
511 511 2 511 16 511 32 511 511
16 2 64 4 511 8 128 32 256
511 511 4 511 511 511 32 128 8
Output
The first one to three lines are the new values to replace the ones given in the input. The number of the lines must be the same as the number of fields in the first input line, and each line must contain nine values separated by whitespace (leading/trailing whitespace gets ignored).
If some field appears in more than one data line, the corresponding values are bitwise anded together. For example, if the initial line of your program's input was
R1 C1
and the first two line of your output read (with question marks replacing values that are irrelevant for this example — of course your code may not actually output question marks here)
3 ? ? ? ? ? ? ? ?
5 ? ? ? ? ? ? ? ?
then the upper left value us the Sudoku storage field will be 3 & 5
, that is, 1
Following those data lines, there will be a single line containing either the single word STOP
, in which case the run loop is terminated and the resulting field is created, or a line containing one to three whitespace separated words requesting data to be served in the next run, that is, the words to be presented in the first line of the next run of the program.
Scoring:
The score for qualifying entries is calculated as follows (lower score is better):
- You get 1 score point for each run of your program.
- You get 5 score points for each set bit in the final representation of your Sudoku field
- At the end, subtract 45 (because a perfectly solved Sudoku will have nine bits set; if your program leaves less bits set, it will be disqualified anyway).
The total score is then calculated as weighted mean of the test cases, where the difficulty is used as weight, rounded up to the next integer. That is, if $d_k$ is the difficulty assigned to test case $k$, and $S_k$ is the score you achieved at test case $k$, your total score is
$$S = \left\lceil \frac{\sum_k d_k S_k}{\sum_k d_k}\right\rceil$$
Sandbox question: Should I change the relative weight of program runs versus unsolved fields? And is the difficulty weighting a good idea, or should I simply add up all scores?
Test cases:
(Hardness as reported by GNOME Sudoku)
Test case 1: Easy (0.17)
4.5|.7.|89.
..2|.5.|6..
..7|9..|542
---+---+---
..3|5.6|489
...|3.8|...
684|7.9|1..
---+---+---
238|..5|9..
..6|.9.|3..
.79|.3.|2.1
Solution:
415|672|893
892|453|617
367|981|542
---+---+---
723|516|489
951|348|726
684|729|135
---+---+---
238|165|974
146|297|358
579|834|261
Test case 2: Hard (0.63)
.6.|52.|..8
7..|...|9.2
.82|71.|56.
---+---+---
.59|...|..6
.76|...|14.
8..|...|72.
---+---+---
.18|.36|25.
6.3|...|..1
5..|.41|.9.
Solution:
961|524|378
745|683|912
382|719|564
---+---+---
159|472|836
276|398|145
834|165|729
---+---+---
418|936|257
693|257|481
527|841|693
Test case 3: Very hard (0.96)
.35|.94|...
..8|.53|..9
4..|8..|...
---+---+---
..1|9..|.85
..9|1.5|3..
54.|..8|9..
---+---+---
...|..7|..1
6..|58.|7..
...|41.|82.
Solution
135|294|678
268|753|149
497|861|532
---+---+---
371|946|285
829|175|364
546|328|917
---+---+---
982|637|451
614|582|793
753|419|826
Sandbox question: Should I add more test cases?