236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
---
|
|
id: 5a23c84252665b21eecc803c
|
|
title: Sudoku
|
|
challengeType: 5
|
|
---
|
|
|
|
## Description
|
|
<section id='description'>
|
|
Write a function to solve a partially filled-in normal 9x9 <a href="https://en.wikipedia.org/wiki/Sudoku">Sudoku</a> grid and return the result. The blank fields are represented by 0s.
|
|
<a href="https://en.wikipedia.org/wiki/Algorithmics_of_sudoku">Algorithmics of Sudoku</a> may help implement this.
|
|
</section>
|
|
|
|
## Instructions
|
|
<section id='instructions'>
|
|
|
|
</section>
|
|
|
|
## Tests
|
|
<section id='tests'>
|
|
|
|
``` yml
|
|
tests:
|
|
- text: <code>solveSudoku</code> should be a function.
|
|
testString: assert(typeof solveSudoku == 'function', '<code>solveSudoku</code> should be a function.');
|
|
- text: <code>solveSudoku([[8, 1, 9, -1, -1, 5, -1, -1, -1],[-1, -1, 2, -1, -1, -1, 7, 5, -1],[-1, 3, 7, 1, -1, 4, -1, 6, -1],[4, -1, -1, 5, 9, -1, 1, -1, -1],[7, -1, -1, 3, -1, 8, -1, -1, 2],[-1, -1, 3, -1, 6, 2, -1, -1, 7],[-1, 5, -1, 7, -1, 9, 2, 1, -1],[-1, 6, 4, -1, -1, -1, 9, -1, -1],[-1, -1, -1, 2, -1, -1, 4, 3, 8]])</code> should return a array.
|
|
testString: assert(Array.isArray(solveSudoku([[8, 1, 9, -1, -1, 5, -1, -1, -1], [-1, -1, 2, -1, -1, -1, 7, 5, -1], [-1, 3, 7, 1, -1, 4, -1, 6, -1], [4, -1, -1, 5, 9, -1, 1, -1, -1], [7, -1, -1, 3, -1, 8, -1, -1, 2], [-1, -1, 3, -1, 6, 2, -1, -1, 7], [-1, 5, -1, 7, -1, 9, 2, 1, -1], [-1, 6, 4, -1, -1, -1, 9, -1, -1], [-1, -1, -1, 2, -1, -1, 4, 3, 8]])), '<code>solveSudoku([[8, 1, 9, -1, -1, 5, -1, -1, -1],[-1, -1, 2, -1, -1, -1, 7, 5, -1],[-1, 3, 7, 1, -1, 4, -1, 6, -1],[4, -1, -1, 5, 9, -1, 1, -1, -1],[7, -1, -1, 3, -1, 8, -1, -1, 2],[-1, -1, 3, -1, 6, 2, -1, -1, 7],[-1, 5, -1, 7, -1, 9, 2, 1, -1],[-1, 6, 4, -1, -1, -1, 9, -1, -1],[-1, -1, -1, 2, -1, -1, 4, 3, 8]])</code> should return a array.');
|
|
- text: <code>solveSudoku([[8, 1, 9, -1, -1, 5, -1, -1, -1],[-1, -1, 2, -1, -1, -1, 7, 5, -1],[-1, 3, 7, 1, -1, 4, -1, 6, -1],[4, -1, -1, 5, 9, -1, 1, -1, -1],[7, -1, -1, 3, -1, 8, -1, -1, 2],[-1, -1, 3, -1, 6, 2, -1, -1, 7],[-1, 5, -1, 7, -1, 9, 2, 1, -1],[-1, 6, 4, -1, -1, -1, 9, -1, -1],[-1, -1, -1, 2, -1, -1, 4, 3, 8]])</code> should return <code>[[8, 1, 9, 6, 7, 5, 3, 2, 4],[6, 4, 2, 9, 8, 3, 7, 5, 1],[5, 3, 7, 1, 2, 4, 8, 6, 9],[4, 2, 6, 5, 9, 7, 1, 8, 3],[7, 9, 5, 3, 1, 8, 6, 4, 2],[1, 8, 3, 4, 6, 2, 5, 9, 7],[3, 5, 8, 7, 4, 9, 2, 1, 6],[2, 6, 4, 8, 3, 1, 9, 7, 5],[9, 7, 1, 2, 5, 6, 4, 3, 8]]</code>.
|
|
testString: assert.deepEqual(solveSudoku([[8, 1, 9, -1, -1, 5, -1, -1, -1], [-1, -1, 2, -1, -1, -1, 7, 5, -1], [-1, 3, 7, 1, -1, 4, -1, 6, -1], [4, -1, -1, 5, 9, -1, 1, -1, -1], [7, -1, -1, 3, -1, 8, -1, -1, 2], [-1, -1, 3, -1, 6, 2, -1, -1, 7], [-1, 5, -1, 7, -1, 9, 2, 1, -1], [-1, 6, 4, -1, -1, -1, 9, -1, -1], [-1, -1, -1, 2, -1, -1, 4, 3, 8]]), [[8, 1, 9, 6, 7, 5, 3, 2, 4], [6, 4, 2, 9, 8, 3, 7, 5, 1], [5, 3, 7, 1, 2, 4, 8, 6, 9], [4, 2, 6, 5, 9, 7, 1, 8, 3], [7, 9, 5, 3, 1, 8, 6, 4, 2], [1, 8, 3, 4, 6, 2, 5, 9, 7], [3, 5, 8, 7, 4, 9, 2, 1, 6], [2, 6, 4, 8, 3, 1, 9, 7, 5], [9, 7, 1, 2, 5, 6, 4, 3, 8]], '<code>solveSudoku([[8, 1, 9, -1, -1, 5, -1, -1, -1],[-1, -1, 2, -1, -1, -1, 7, 5, -1],[-1, 3, 7, 1, -1, 4, -1, 6, -1],[4, -1, -1, 5, 9, -1, 1, -1, -1],[7, -1, -1, 3, -1, 8, -1, -1, 2],[-1, -1, 3, -1, 6, 2, -1, -1, 7],[-1, 5, -1, 7, -1, 9, 2, 1, -1],[-1, 6, 4, -1, -1, -1, 9, -1, -1],[-1, -1, -1, 2, -1, -1, 4, 3, 8]])</code> should return <code>[[8, 1, 9, 6, 7, 5, 3, 2, 4],[6, 4, 2, 9, 8, 3, 7, 5, 1],[5, 3, 7, 1, 2, 4, 8, 6, 9],[4, 2, 6, 5, 9, 7, 1, 8, 3],[7, 9, 5, 3, 1, 8, 6, 4, 2],[1, 8, 3, 4, 6, 2, 5, 9, 7],[3, 5, 8, 7, 4, 9, 2, 1, 6],[2, 6, 4, 8, 3, 1, 9, 7, 5],[9, 7, 1, 2, 5, 6, 4, 3, 8]]</code>.');
|
|
- text: <code>solveSudoku([[5, 3, -1, -1, 2, 4, 7, -1, -1],[-1, -1, 2, -1, -1, -1, 8, -1, -1],[1, -1, -1, 7, -1, 3, 9, -1, 2],[-1, -1, 8, -1, 7, 2, -1, 4, 9],[-1, 2, -1, 9, 8, -1, -1, 7, -1],[7, 9, -1, -1, -1, -1, -1, 8, -1],[-1, -1, -1, -1, 3, -1, 5, -1, 6],[9, 6, -1, -1, 1, -1, 3, -1, -1],[-1, 5, -1, 6, 9, -1, -1, 1, -1]])</code> should return <code>[[5, 3, 9, 8, 2, 4, 7, 6, 1],[6, 7, 2, 1, 5, 9, 8, 3, 4],[1, 8, 4, 7, 6, 3, 9, 5, 2],[3, 1, 8, 5, 7, 2, 6, 4, 9],[4, 2, 5, 9, 8, 6, 1, 7, 3],[7, 9, 6, 3, 4, 1, 2, 8, 5],[8, 4, 1, 2, 3, 7, 5, 9, 6],[9, 6, 7, 4, 1, 5, 3, 2, 8],[2, 5, 3, 6, 9, 8, 4, 1, 7]]</code>.
|
|
testString: assert.deepEqual(solveSudoku([[5, 3, -1, -1, 2, 4, 7, -1, -1], [-1, -1, 2, -1, -1, -1, 8, -1, -1], [1, -1, -1, 7, -1, 3, 9, -1, 2], [-1, -1, 8, -1, 7, 2, -1, 4, 9], [-1, 2, -1, 9, 8, -1, -1, 7, -1], [7, 9, -1, -1, -1, -1, -1, 8, -1], [-1, -1, -1, -1, 3, -1, 5, -1, 6], [9, 6, -1, -1, 1, -1, 3, -1, -1], [-1, 5, -1, 6, 9, -1, -1, 1, -1]]), [[5, 3, 9, 8, 2, 4, 7, 6, 1], [6, 7, 2, 1, 5, 9, 8, 3, 4], [1, 8, 4, 7, 6, 3, 9, 5, 2], [3, 1, 8, 5, 7, 2, 6, 4, 9], [4, 2, 5, 9, 8, 6, 1, 7, 3], [7, 9, 6, 3, 4, 1, 2, 8, 5], [8, 4, 1, 2, 3, 7, 5, 9, 6], [9, 6, 7, 4, 1, 5, 3, 2, 8], [2, 5, 3, 6, 9, 8, 4, 1, 7]], '<code>solveSudoku([[5, 3, -1, -1, 2, 4, 7, -1, -1],[-1, -1, 2, -1, -1, -1, 8, -1, -1],[1, -1, -1, 7, -1, 3, 9, -1, 2],[-1, -1, 8, -1, 7, 2, -1, 4, 9],[-1, 2, -1, 9, 8, -1, -1, 7, -1],[7, 9, -1, -1, -1, -1, -1, 8, -1],[-1, -1, -1, -1, 3, -1, 5, -1, 6],[9, 6, -1, -1, 1, -1, 3, -1, -1],[-1, 5, -1, 6, 9, -1, -1, 1, -1]])</code> should return <code>[[5, 3, 9, 8, 2, 4, 7, 6, 1],[6, 7, 2, 1, 5, 9, 8, 3, 4],[1, 8, 4, 7, 6, 3, 9, 5, 2],[3, 1, 8, 5, 7, 2, 6, 4, 9],[4, 2, 5, 9, 8, 6, 1, 7, 3],[7, 9, 6, 3, 4, 1, 2, 8, 5],[8, 4, 1, 2, 3, 7, 5, 9, 6],[9, 6, 7, 4, 1, 5, 3, 2, 8],[2, 5, 3, 6, 9, 8, 4, 1, 7]]</code>.');
|
|
- text: <code>solveSudoku([[-1, -1, 3, -1, 2, -1, 6, -1, -1],[9, -1, -1, 3, -1, 5, -1, -1, 1],[-1, -1, 1, 8, -1, 6, 4, -1, -1],[-1, -1, 8, 1, -1, 2, 9, -1, -1],[7, -1, -1, -1, -1, -1, -1, -1, 8],[-1, -1, 6, 7, -1, 8, 2, -1, -1],[-1, -1, 2, 6, -1, 9, 5, -1, -1],[8, -1, -1, 2, -1, 3, -1, -1, 9],[-1, -1, 5, -1, 1, -1, 3, -1, -1]])</code> should return <code>[[4, 8, 3, 9, 2, 1, 6, 5, 7],[9, 6, 7, 3, 4, 5, 8, 2, 1],[2, 5, 1, 8, 7, 6, 4, 9, 3],[5, 4, 8, 1, 3, 2, 9, 7, 6],[7, 2, 9, 5, 6, 4, 1, 3, 8],[1, 3, 6, 7, 9, 8, 2, 4, 5],[3, 7, 2, 6, 8, 9, 5, 1, 4],[8, 1, 4, 2, 5, 3, 7, 6, 9],[6, 9, 5, 4, 1, 7, 3, 8, 2]]</code>.
|
|
testString: assert.deepEqual(solveSudoku([[-1, -1, 3, -1, 2, -1, 6, -1, -1], [9, -1, -1, 3, -1, 5, -1, -1, 1], [-1, -1, 1, 8, -1, 6, 4, -1, -1], [-1, -1, 8, 1, -1, 2, 9, -1, -1], [7, -1, -1, -1, -1, -1, -1, -1, 8], [-1, -1, 6, 7, -1, 8, 2, -1, -1], [-1, -1, 2, 6, -1, 9, 5, -1, -1], [8, -1, -1, 2, -1, 3, -1, -1, 9], [-1, -1, 5, -1, 1, -1, 3, -1, -1]]), [[4, 8, 3, 9, 2, 1, 6, 5, 7], [9, 6, 7, 3, 4, 5, 8, 2, 1], [2, 5, 1, 8, 7, 6, 4, 9, 3], [5, 4, 8, 1, 3, 2, 9, 7, 6], [7, 2, 9, 5, 6, 4, 1, 3, 8], [1, 3, 6, 7, 9, 8, 2, 4, 5], [3, 7, 2, 6, 8, 9, 5, 1, 4], [8, 1, 4, 2, 5, 3, 7, 6, 9], [6, 9, 5, 4, 1, 7, 3, 8, 2]], '<code>solveSudoku([[-1, -1, 3, -1, 2, -1, 6, -1, -1],[9, -1, -1, 3, -1, 5, -1, -1, 1],[-1, -1, 1, 8, -1, 6, 4, -1, -1],[-1, -1, 8, 1, -1, 2, 9, -1, -1],[7, -1, -1, -1, -1, -1, -1, -1, 8],[-1, -1, 6, 7, -1, 8, 2, -1, -1],[-1, -1, 2, 6, -1, 9, 5, -1, -1],[8, -1, -1, 2, -1, 3, -1, -1, 9],[-1, -1, 5, -1, 1, -1, 3, -1, -1]])</code> should return <code>[[4, 8, 3, 9, 2, 1, 6, 5, 7],[9, 6, 7, 3, 4, 5, 8, 2, 1],[2, 5, 1, 8, 7, 6, 4, 9, 3],[5, 4, 8, 1, 3, 2, 9, 7, 6],[7, 2, 9, 5, 6, 4, 1, 3, 8],[1, 3, 6, 7, 9, 8, 2, 4, 5],[3, 7, 2, 6, 8, 9, 5, 1, 4],[8, 1, 4, 2, 5, 3, 7, 6, 9],[6, 9, 5, 4, 1, 7, 3, 8, 2]]</code>.');
|
|
```
|
|
|
|
</section>
|
|
|
|
## Challenge Seed
|
|
<section id='challengeSeed'>
|
|
<div id='js-seed'>
|
|
|
|
```js
|
|
function solveSudoku (puzzle) {
|
|
// Good luck!
|
|
}
|
|
```
|
|
|
|
</div>
|
|
</section>
|
|
|
|
## Solution
|
|
<section id='solution'>
|
|
|
|
```js
|
|
function solveSudoku (puzzle) {
|
|
var solution;
|
|
|
|
class DoX {
|
|
constructor(V, H) {
|
|
this.V = V;
|
|
this.L = this;
|
|
this.R = this;
|
|
this.U = this;
|
|
this.D = this;
|
|
this.S = 1;
|
|
this.H = H || this;
|
|
H && (H.S += 1);
|
|
}
|
|
}
|
|
|
|
const addRight = (e, n) => {
|
|
n.R = e.R;
|
|
n.L = e;
|
|
e.R.L = n;
|
|
return e.R = n;
|
|
};
|
|
|
|
const addBelow = (e, n) => {
|
|
n.D = e.D;
|
|
n.U = e;
|
|
e.D.U = n;
|
|
return e.D = n;
|
|
};
|
|
|
|
const search = function(h, s) {
|
|
if (h.R == h) {
|
|
printSol(s);
|
|
} else {
|
|
let c = chooseColumn(h);
|
|
cover(c);
|
|
for (let r = c.D; r != c; r = r.D) {
|
|
s.push(r);
|
|
for (let j = r.R; r != j; j = j.R) {
|
|
cover(j.H);
|
|
}
|
|
search(h, s);
|
|
r = s.pop();
|
|
for (let j = r.R; j != r; j = j.R) {
|
|
uncover(j.H);
|
|
}
|
|
}
|
|
uncover(c);
|
|
}
|
|
};
|
|
|
|
const chooseColumn = h => {
|
|
let s = Number.POSITIVE_INFINITY;
|
|
let c = h;
|
|
for (let j = h.R; j != h; j = j.R) {
|
|
if (j.S < s) {
|
|
c = j;
|
|
s = j.S;
|
|
}
|
|
}
|
|
return c;
|
|
};
|
|
|
|
const cover = c => {
|
|
c.L.R = c.R;
|
|
c.R.L = c.L;
|
|
for (let i = c.D; i != c; i = i.D) {
|
|
for (let j = i.R; j != i; j = j.R) {
|
|
j.U.D = j.D;
|
|
j.D.U = j.U;
|
|
j.H.S = j.H.S - 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
const uncover = c => {
|
|
for (let i = c.U; i != c; i = i.U) {
|
|
for (let j = i.L; i != j; j = j.L) {
|
|
j.H.S = j.H.S + 1;
|
|
j.U.D = j;
|
|
j.D.U = j;
|
|
}
|
|
}
|
|
c.L.R = c;
|
|
c.R.L = c;
|
|
};
|
|
|
|
const printSol = a => {
|
|
solution = a.reduce((p, c) => {
|
|
let [i, v] = c.V.split(':');
|
|
p[i * 1] = v;
|
|
return p;
|
|
}, new Array(a.length).fill('.'));
|
|
};
|
|
|
|
const gridMeta = s => {
|
|
const g = s.split('');
|
|
const cellCount = g.length;
|
|
const tokenCount = Math.sqrt(cellCount);
|
|
const N = Math.sqrt(tokenCount);
|
|
const g2D = g.map(e => isNaN(e * 1) ?
|
|
new Array(tokenCount).fill(1).map((_, i) => i + 1) : [e * 1]);
|
|
return [cellCount, N, tokenCount, g2D];
|
|
};
|
|
|
|
const indexesN = n => i => {
|
|
let c = Math.floor(i / (n * n));
|
|
i %= n * n;
|
|
return [c, i, Math.floor(c / n) * n + Math.floor(i / n)];
|
|
};
|
|
|
|
const reduceGrid = puzString => {
|
|
|
|
const [
|
|
numCells, // The total number of cells in a grid (81 for a 9x9 grid)
|
|
N, // the 'n' value of the grid. (3 for a 9x9 grid)
|
|
U, // The total number of unique tokens to be placed.
|
|
g2D // A 2D array representation of the grid, with each element
|
|
// being an array of candidates for a cell. Known cells are
|
|
// single element arrays.
|
|
] = gridMeta(puzString);
|
|
|
|
const getIndex = indexesN(N);
|
|
|
|
const headRow = new Array(4 * numCells)
|
|
.fill('')
|
|
.map((_, i) => new DoX(`H${i}`));
|
|
|
|
let H = new DoX('ROOT');
|
|
headRow.reduce((p, c) => addRight(p, c), H);
|
|
|
|
for (let i = 0; i < numCells; i++) {
|
|
const [ri, ci, bi] = getIndex(i);
|
|
g2D[i].forEach(num => {
|
|
let id = `${i}:${num}`;
|
|
let candIdx = num - 1;
|
|
|
|
// The 4 columns that we will populate.
|
|
const A = headRow[i];
|
|
const B = headRow[numCells + candIdx + (ri * U)];
|
|
const C = headRow[(numCells * 2) + candIdx + (ci * U)];
|
|
const D = headRow[(numCells * 3) + candIdx + (bi * U)];
|
|
|
|
// The Row-Column Constraint
|
|
let rcc = addBelow(A.U, new DoX(id, A));
|
|
|
|
// The Row-Number Constraint
|
|
let rnc = addBelow(B.U, addRight(rcc, new DoX(id, B)));
|
|
|
|
// The Column-Number Constraint
|
|
let cnc = addBelow(C.U, addRight(rnc, new DoX(id, C)));
|
|
|
|
// The Block-Number Constraint
|
|
addBelow(D.U, addRight(cnc, new DoX(id, D)));
|
|
});
|
|
}
|
|
search(H, []);
|
|
};
|
|
|
|
var stringPuzzle = "";
|
|
|
|
for (var i = 0; i < puzzle.length; i++) {
|
|
puzzle[i].forEach(function(e) {
|
|
if (e == -1)
|
|
stringPuzzle += ".";
|
|
else
|
|
stringPuzzle += e;
|
|
})
|
|
}
|
|
|
|
reduceGrid(stringPuzzle)
|
|
|
|
var result = [];
|
|
|
|
for (var i = 0; i < 9; i++) {
|
|
result.push(solution.slice(i * 9, (i + 1) * 9).map(e => parseInt(e)))
|
|
}
|
|
|
|
return result;
|
|
}
|
|
```
|
|
|
|
</section> |