finish implementing sudoku-solver.js and writing unit test
This commit is contained in:
+164
-86
@@ -1,3 +1,7 @@
|
||||
import { columns } from '../models/columns';
|
||||
import { rows } from '../models/rows';
|
||||
import { grids } from '../models/grids';
|
||||
|
||||
class SudokuSolver {
|
||||
|
||||
validate(puzzleString) {
|
||||
@@ -8,19 +12,6 @@ class SudokuSolver {
|
||||
|
||||
checkRowPlacement(puzzleString, row, column, value) {
|
||||
|
||||
// rows
|
||||
const rows = {
|
||||
A: [],
|
||||
B: [],
|
||||
C: [],
|
||||
D: [],
|
||||
E: [],
|
||||
F: [],
|
||||
G: [],
|
||||
H: [],
|
||||
I: [],
|
||||
};
|
||||
|
||||
// rowKeys
|
||||
const rowKeys = Object.keys(rows);
|
||||
|
||||
@@ -50,18 +41,6 @@ class SudokuSolver {
|
||||
|
||||
checkColPlacement(puzzleString, row, column, value) {
|
||||
|
||||
const columns = {
|
||||
1: [],
|
||||
2: [],
|
||||
3: [],
|
||||
4: [],
|
||||
5: [],
|
||||
6: [],
|
||||
7: [],
|
||||
8: [],
|
||||
9: [],
|
||||
};
|
||||
|
||||
const columnKeys = Object.keys(columns);
|
||||
|
||||
columnKeys.forEach((n, i) => {
|
||||
@@ -125,49 +104,15 @@ class SudokuSolver {
|
||||
|
||||
checkRegionPlacement(puzzleString, row, column, value) {
|
||||
|
||||
// build location from row and column values
|
||||
const location = row + column;
|
||||
|
||||
let grids = {
|
||||
gridOne: {
|
||||
locations: ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
|
||||
values: []
|
||||
},
|
||||
gridTwo: {
|
||||
locations: ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
|
||||
values: []
|
||||
},
|
||||
gridThree: {
|
||||
locations: ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
|
||||
values: []
|
||||
},
|
||||
gridFour: {
|
||||
locations: ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
|
||||
values: []
|
||||
},
|
||||
gridFive: {
|
||||
locations: ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
|
||||
values: []
|
||||
},
|
||||
gridSix: {
|
||||
locations: ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
|
||||
values: []
|
||||
},
|
||||
gridSeven: {
|
||||
locations: ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
|
||||
values: []
|
||||
},
|
||||
gridEight: {
|
||||
locations: ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
|
||||
values: []
|
||||
},
|
||||
gridNine: {
|
||||
locations: ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
|
||||
values: []
|
||||
}
|
||||
};
|
||||
|
||||
// get keys for grids object
|
||||
const gridKeys = Object.keys(grids);
|
||||
|
||||
// for each grid key loop through
|
||||
// and slice values from puzzle string
|
||||
// to create each grids corresponding grid values
|
||||
gridKeys.forEach((key, i) => {
|
||||
|
||||
let offset = 0;
|
||||
@@ -184,58 +129,191 @@ class SudokuSolver {
|
||||
|
||||
}
|
||||
|
||||
let values = [];
|
||||
|
||||
for (let j = 0; j < 3; j++) {
|
||||
|
||||
const start = (i * 3) + (9 * (j + offset));
|
||||
const end = start + 3;
|
||||
const gridValues = puzzleString.slice(start, end).split('');
|
||||
|
||||
grids[key].values = grids[key].values.concat(gridValues);
|
||||
values = values.concat(gridValues);
|
||||
|
||||
}
|
||||
|
||||
grids[key].values = values;
|
||||
|
||||
});
|
||||
|
||||
let gridValues = [];
|
||||
let locations;
|
||||
|
||||
// for each grid key loop through grids
|
||||
// find grid that has the location that was passed
|
||||
// get that grids grid values
|
||||
gridKeys.forEach(key => {
|
||||
|
||||
const grid = grids[key]
|
||||
|
||||
if (grid.locations.includes(location)) {
|
||||
gridValues = gridValues.concat(grid.values);
|
||||
locations = grid.locations;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
let existingValue;
|
||||
|
||||
switch (row) {
|
||||
case 'A':
|
||||
case 'D':
|
||||
case 'H':
|
||||
existingValue = gridValues[(parseInt(column) - 1)];
|
||||
break;
|
||||
case 'B':
|
||||
case 'E':
|
||||
case 'H':
|
||||
existingValue = gridValues[3 + (parseInt(column) - 1)];
|
||||
break;
|
||||
case 'C':
|
||||
case 'F':
|
||||
case 'I':
|
||||
existingValue = gridValues[6 + (parseInt(column) - 1)];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// get existing value from grid values based on location passed
|
||||
let existingValue = gridValues[locations.indexOf(location)];
|
||||
|
||||
// make sure placement location doesn't contain a value or contains same value
|
||||
// make sure placements grid does not already have value.
|
||||
return (existingValue == '.' || existingValue == value) && !gridValues.includes(value);
|
||||
|
||||
}
|
||||
|
||||
solve(puzzleString) {
|
||||
|
||||
if (!this.validate(puzzleString)) return false;
|
||||
|
||||
// create character array
|
||||
let puzzle = puzzleString.split('');
|
||||
|
||||
// transform character array into
|
||||
// 2-d array with each element representing a row
|
||||
// that contains an array of column values
|
||||
let generateBoard = (puzzleValues) => {
|
||||
|
||||
let board = [[], [], [], [], [], [], [], [], []]
|
||||
let boardRow = -1
|
||||
|
||||
for (let i = 0; i < puzzleValues.length; i++) {
|
||||
|
||||
// when you've reached a multiple of 9
|
||||
// it means you are at the beginning
|
||||
// of a new row so need to increment the
|
||||
// board row
|
||||
if (i % 9 === 0) {
|
||||
|
||||
boardRow = boardRow + 1;
|
||||
|
||||
}
|
||||
|
||||
// push the current puzzle value into the
|
||||
// correct board rows array
|
||||
board[boardRow].push(puzzleValues[i]);
|
||||
}
|
||||
|
||||
// return the board
|
||||
return board;
|
||||
}
|
||||
|
||||
let canPlace = (board, row, col, value) => {
|
||||
|
||||
// verify that the value can be
|
||||
// placed in the given column
|
||||
for (let i = 0; i < 9; i++) {
|
||||
|
||||
if (board[i][col] == value) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// verify that the value can be
|
||||
// placed in the given row
|
||||
for (let j = 0; j < 9; j++) {
|
||||
|
||||
if (board[row][j] == value) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let boxTopRow = parseInt(row / 3) * 3;
|
||||
let boxLeftColumn = parseInt(col / 3) * 3;
|
||||
|
||||
// verify that the value can be
|
||||
// placed in the given grid
|
||||
for (let k = boxTopRow; k < boxTopRow + 3; k++) {
|
||||
|
||||
for (let l = boxLeftColumn; l < boxLeftColumn + 3; l++) {
|
||||
|
||||
if (board[k][l] == value) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
let solveFromCell = (board, row, col) => {
|
||||
|
||||
// reset column and increment
|
||||
// row when end of row reached
|
||||
if (col === 9) {
|
||||
|
||||
col = 0;
|
||||
row++;
|
||||
|
||||
}
|
||||
|
||||
// return solved board
|
||||
// when completed all rows
|
||||
if (row === 9) {
|
||||
|
||||
return board;
|
||||
|
||||
}
|
||||
|
||||
// if cell is already filled move to
|
||||
// next cell
|
||||
if (board[row][col] != '.') {
|
||||
|
||||
return solveFromCell(board, row, col + 1);
|
||||
|
||||
}
|
||||
|
||||
// attempt to place values 1 - 9 in given
|
||||
// cell and if a value can be placed
|
||||
// perform a recursive call to check the rest
|
||||
// of the cells can be completed without issue.
|
||||
for (let i = 1; i < 10; i++) {
|
||||
|
||||
let valueToPlace = i.toString();
|
||||
|
||||
if (canPlace(board, row, col, valueToPlace)) {
|
||||
|
||||
board[row][col] = valueToPlace;
|
||||
|
||||
if (solveFromCell(board, row, col + 1) != false) {
|
||||
|
||||
return board;
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
board[row][col] = '.';
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// get the solution for the given puzzle
|
||||
let board = generateBoard(puzzle);
|
||||
let solution = solveFromCell(board, 0, 0);
|
||||
|
||||
return solution.flat().join('');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const columns = {
|
||||
1: [],
|
||||
2: [],
|
||||
3: [],
|
||||
4: [],
|
||||
5: [],
|
||||
6: [],
|
||||
7: [],
|
||||
8: [],
|
||||
9: [],
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
export const grids = {
|
||||
gridOne: {
|
||||
locations: ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3'],
|
||||
values: []
|
||||
},
|
||||
gridTwo: {
|
||||
locations: ['A4', 'A5', 'A6', 'B4', 'B5', 'B6', 'C4', 'C5', 'C6'],
|
||||
values: []
|
||||
},
|
||||
gridThree: {
|
||||
locations: ['A7', 'A8', 'A9', 'B7', 'B8', 'B9', 'C7', 'C8', 'C9'],
|
||||
values: []
|
||||
},
|
||||
gridFour: {
|
||||
locations: ['D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3'],
|
||||
values: []
|
||||
},
|
||||
gridFive: {
|
||||
locations: ['D4', 'D5', 'D6', 'E4', 'E5', 'E6', 'F4', 'F5', 'F6'],
|
||||
values: []
|
||||
},
|
||||
gridSix: {
|
||||
locations: ['D7', 'D8', 'D9', 'E7', 'E8', 'E9', 'F7', 'F8', 'F9'],
|
||||
values: []
|
||||
},
|
||||
gridSeven: {
|
||||
locations: ['G1', 'G2', 'G3', 'H1', 'H2', 'H3', 'I1', 'I2', 'I3'],
|
||||
values: []
|
||||
},
|
||||
gridEight: {
|
||||
locations: ['G4', 'G5', 'G6', 'H4', 'H5', 'H6', 'I4', 'I5', 'I6'],
|
||||
values: []
|
||||
},
|
||||
gridNine: {
|
||||
locations: ['G7', 'G8', 'G9', 'H7', 'H8', 'H9', 'I7', 'I8', 'I9'],
|
||||
values: []
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
export const rows = {
|
||||
A: [],
|
||||
B: [],
|
||||
C: [],
|
||||
D: [],
|
||||
E: [],
|
||||
F: [],
|
||||
G: [],
|
||||
H: [],
|
||||
I: [],
|
||||
};
|
||||
+30
-1
@@ -51,7 +51,7 @@ suite('UnitTests', () => {
|
||||
const input = puzzlesAndSolutions[0][0];
|
||||
const row = 'A';
|
||||
const column = '2';
|
||||
const value = '8';
|
||||
const value = '1';
|
||||
|
||||
assert.equal(solver.checkRowPlacement(input, row, column, value), false);
|
||||
done();
|
||||
@@ -106,5 +106,34 @@ suite('UnitTests', () => {
|
||||
|
||||
});
|
||||
|
||||
test('solve(puzzleString) valid puzzle string', (done) => {
|
||||
|
||||
const puzzle = puzzlesAndSolutions[0][0];
|
||||
const solution = puzzlesAndSolutions[0][1];
|
||||
|
||||
assert.equal(solver.solve(puzzle), solution);
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
test('solve(puzzleString) invalid puzzle string', (done) => {
|
||||
|
||||
const puzzle = puzzlesAndSolutions[0][0]+'0';
|
||||
|
||||
assert.equal(solver.solve(puzzle), false);
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
test('solve(puzzleString) uncomplete puzzle yields expected solution', (done) => {
|
||||
|
||||
const puzzle = puzzlesAndSolutions[0][0];
|
||||
const solution = puzzlesAndSolutions[0][1];
|
||||
|
||||
assert.equal(solver.solve(puzzle), solution);
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user