-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Doding dojo JS edition - Sudoku solver Kata. Artur Wodarz solution #1
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,3 +18,5 @@ Return of the program should be a string as well but with all numbers filled out | |
## Running tests | ||
|
||
npm test | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"scripts": { | ||
"test": "mocha *.test.js" | ||
"test": "mocha test/*.test.js" | ||
}, | ||
"dependencies": { | ||
"chai": "^4.2.0", | ||
|
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
function inRange(low, high, num) { | ||
return num >= low && num <= high ? true : false; | ||
} | ||
|
||
function createMatrix(rows) { | ||
return Array.from(Array(rows), row => []); | ||
} | ||
|
||
function extractBlock(x, y, matrix, blockSize = 3) { | ||
if (!inRange(0, matrix.length - 1, x) || y < 0) throw new RangeError(); | ||
const blockStart = { row: x - (x % blockSize), column: y - (y % blockSize) }; | ||
let block = []; | ||
for (let i = blockStart.row; i < blockStart.row + blockSize; i++) { | ||
if (y >= matrix[i].length) throw new RangeError(); | ||
for (let j = blockStart.column; j < blockStart.column + blockSize; j++) { | ||
block.push(matrix[i][j]); | ||
} | ||
} | ||
return block; | ||
} | ||
|
||
function extractRow(rowIndex, matrix) { | ||
let row = []; | ||
if (!inRange(0, matrix.length - 1, rowIndex)) throw new RangeError(); | ||
for (let i = 0; i < matrix.length; i++) { | ||
row.push(matrix[rowIndex][i]); | ||
} | ||
return row; | ||
} | ||
|
||
function extractColumn(columnIndex, matrix) { | ||
let column = []; | ||
if (!inRange(0, matrix.length - 1, columnIndex)) throw new RangeError(); | ||
for (let i = 0; i < matrix.length; i++) { | ||
column.push(matrix[i][columnIndex]); | ||
} | ||
return column; | ||
} | ||
|
||
function getEmptyCells(matrix) { | ||
let emptyCells = []; | ||
matrix.forEach((arr, rowIndex) => { | ||
arr.forEach((value, colIndex) => { | ||
if (value === null) emptyCells.push({row: rowIndex, col: colIndex}); | ||
}); | ||
}); | ||
return emptyCells; | ||
} | ||
|
||
|
||
function findUniqueInt(min, max, array){ | ||
const values = new Set(array); | ||
for (let i = min; i <= max; i++){ | ||
if (!values.has(i)){ | ||
return i; | ||
} | ||
} | ||
return null; | ||
} | ||
module.exports = { | ||
createMatrix, | ||
inRange, | ||
extractBlock, | ||
extractRow, | ||
extractColumn, | ||
getEmptyCells, | ||
findUniqueInt | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
"use strict"; | ||
|
||
const parser = require("./sudokuParser"); | ||
const helper = require("./helper"); | ||
|
||
function getRelatedCells(cell, sudokuMatrix) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When reading body of |
||
return new Array() | ||
.concat(helper.extractRow(cell.row, sudokuMatrix)) | ||
.concat(helper.extractColumn(cell.col, sudokuMatrix)) | ||
.concat(helper.extractBlock(cell.row, cell.col, sudokuMatrix, 3)); | ||
} | ||
|
||
function solve(sudokuString) { | ||
let sudokuMatrix = parser.parseToArray(sudokuString); | ||
const modifiableCells = helper.getEmptyCells(sudokuMatrix); | ||
let nowModified = 0; | ||
|
||
while (helper.inRange(0, modifiableCells.length - 1, nowModified)) { | ||
let x = modifiableCells[nowModified].row; | ||
let y = modifiableCells[nowModified].col; | ||
if (sudokuMatrix[x][y] == null) sudokuMatrix[x][y] = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you only set the cell to |
||
let newUnique = helper.findUniqueInt( | ||
sudokuMatrix[x][y], | ||
9, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a magic number. It also can be retrieved from parsed matrix dimension. |
||
getRelatedCells(modifiableCells[nowModified], sudokuMatrix) | ||
); | ||
|
||
if (newUnique == null) { | ||
sudokuMatrix[x][y] = null; | ||
nowModified--; | ||
} else { | ||
sudokuMatrix[x][y] = newUnique; | ||
nowModified++; | ||
} | ||
} | ||
if (nowModified < 0) return "Unsolvable sudoku"; | ||
else return sudokuMatrix.join().replace(/,/g, ""); | ||
} | ||
exports.solve = solve; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
"use strict"; | ||
|
||
const helper = require("./helper"); | ||
|
||
function parseToArray(sudokuAsString) { | ||
let sudokuArray = helper.createMatrix(9); | ||
sudokuAsString.split("").forEach((el, index) => { | ||
sudokuArray[Math.floor(index / 9)][index % 9] = el != "_" ? parseInt(el) : null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
|
||
}); | ||
return sudokuArray; | ||
} | ||
|
||
|
||
|
||
module.exports = { | ||
parseToArray | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
'use strict'; | ||
|
||
const chai = require('chai'); | ||
require('mocha'); | ||
const helper = require('../src/helper'); | ||
|
||
describe('Create matrix', () => { | ||
it('Should return array', () => chai.assert.isArray(helper.createMatrix())); | ||
it('Should return array contains provided number of elements', () => { | ||
chai.assert.equal(helper.createMatrix(5).length, 5); | ||
}); | ||
}); | ||
|
||
describe('Block extraction', () => { | ||
const n = null; | ||
const sudokuMatrix = [ | ||
[n, n, n, 2, 6, n, 7, n, 1], | ||
[6, 8, n, n, 7, n, n, 9, n], | ||
[1, 9, n, n, n, 4, 5, n, n], | ||
[8, 2, n, 1, n, n, n, 4, n], | ||
[n, n, 4, 6, n, 2, 9, n, n], | ||
[n, 5, n, n, n, 3, n, 2, 8], | ||
[n, n, 9, 3, n, n, n, 7, 4], | ||
[n, 4, n, n, 5, n, n, 3, 6], | ||
[7, n, 3, n, 1, 8, n, n, n] | ||
]; | ||
const blocks = { | ||
'0/0': [n, n, n, 6, 8, n, 1, 9, n], | ||
'0/2': [n, n, n, 6, 8, n, 1, 9, n], | ||
'3/5': [1, n, n, 6, n, 2, n, n, 3], | ||
'7/8': [n, 7, 4, n, 3, 6, n, n, n] | ||
} | ||
|
||
it('Should return 9 elements array', () => { | ||
chai.assert.isArray(helper.extractBlock(1, 2, sudokuMatrix)); | ||
chai.assert.equal(helper.extractBlock(1, 2, sudokuMatrix).length, 9); | ||
}); | ||
it('Should return array of all elements in block', () => { | ||
chai.assert.deepEqual(helper.extractBlock(0, 0, sudokuMatrix), blocks['0/0']); | ||
chai.assert.deepEqual(helper.extractBlock(0, 2, sudokuMatrix), blocks['0/2']); | ||
chai.assert.deepEqual(helper.extractBlock(4, 5, sudokuMatrix), blocks['3/5']); | ||
chai.assert.deepEqual(helper.extractBlock(7, 8, sudokuMatrix), blocks['7/8']); | ||
}) | ||
it('Should throw RangeError if provided coords are out of matrix range', () => { | ||
chai.expect(() => helper.extractBlock(-1, -1, sudokuMatrix)).to.throw(RangeError); | ||
chai.expect(() => helper.extractBlock(9, 5, sudokuMatrix)).to.throw(RangeError); | ||
}) | ||
}); | ||
|
||
describe('Column extraction', () => { | ||
const n = null; | ||
const sudokuMatrix = [ | ||
[n, n, n, 2, 6, n, 7, n, 1], | ||
[6, 8, n, n, 7, n, n, 9, n], | ||
[1, 9, n, n, n, 4, 5, n, n], | ||
[8, 2, n, 1, n, n, n, 4, n], | ||
[n, n, 4, 6, n, 2, 9, n, n], | ||
[n, 5, n, n, n, 3, n, 2, 8], | ||
[n, n, 9, 3, n, n, n, 7, 4], | ||
[n, 4, n, n, 5, n, n, 3, 6], | ||
[7, n, 3, n, 1, 8, n, n, n] | ||
]; | ||
|
||
const columns = { | ||
0: [n, 6, 1, 8, n, n, n, n, 7], | ||
4: [6, 7, n, n, n, n, n, 5, 1], | ||
8: [1, n, n, n, n, 8, 4, 6, n] | ||
}; | ||
it('Should return 9 elements array', () => { | ||
chai.assert.isArray(helper.extractColumn(0, sudokuMatrix)); | ||
chai.assert.equal(helper.extractColumn(0, sudokuMatrix).length, 9); | ||
}); | ||
it('Should return array of all elements in Column', () => { | ||
chai.assert.deepEqual(helper.extractColumn(0, sudokuMatrix), columns[0]); | ||
chai.assert.deepEqual(helper.extractColumn(4, sudokuMatrix), columns[4]); | ||
chai.assert.deepEqual(helper.extractColumn(8, sudokuMatrix), columns[8]); | ||
}) | ||
it('Should throw RangeError if provided coords are out of matrix range', () => { | ||
chai.expect(() => helper.extractColumn(-1, sudokuMatrix)).to.throw(RangeError); | ||
chai.expect(() => helper.extractColumn(9, sudokuMatrix)).to.throw(RangeError); | ||
}) | ||
}); | ||
|
||
describe('Finding nulls in matrix', () => { | ||
const n = null; | ||
const sudokuMatrix = [ | ||
[n, n, n, 2, 6, n, 7, n, 1], | ||
[6, 8, n, n, 7, n, n, 9, n], | ||
[1, 9, n, n, n, 4, 5, n, n], | ||
[8, 2, n, 1, n, n, n, 4, n], | ||
[n, n, 4, 6, n, 2, 9, n, n], | ||
[n, 5, n, n, n, 3, n, 2, 8], | ||
[n, n, 9, 3, n, n, n, 7, 4], | ||
[n, 4, n, n, 5, n, n, 3, 6], | ||
[7, n, 3, n, 1, 8, n, n, n] | ||
]; | ||
|
||
it('Should return 35 elements array', () => { | ||
chai.assert.isArray(helper.getEmptyCells(sudokuMatrix)); | ||
chai.assert.equal(helper.getEmptyCells(sudokuMatrix).length, 45); | ||
}); | ||
it('Every element should contains row and col keys', () => { | ||
helper.getEmptyCells(sudokuMatrix).forEach(el => chai.expect(el).to.have.all.keys('row', 'col')); | ||
}); | ||
}); | ||
|
||
describe('Finding first integer within given range that does not exists in providen array', () => { | ||
it ('Should return first unique number within prividen range', () => { | ||
const arrWithUniques = [2, 3, 4, 8, 9.2 ,10]; | ||
chai.assert.equal(helper.findUniqueInt(1, 11, arrWithUniques), 1, 'Testing min as ununique number'); | ||
chai.assert.equal(helper.findUniqueInt(8, 11, arrWithUniques), 9, 'Function should ignore float numbers'); | ||
}); | ||
it ('Should return null if could not find any unique number', () => { | ||
const arrWithoutUniques = [1, 2, 3, 4, 5]; | ||
chai.assert.equal(helper.findUniqueInt(1, 5, arrWithoutUniques), null) | ||
}); | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
"use strict"; | ||
|
||
const chai = require("chai"); | ||
require("mocha"); | ||
const solver = require('../src/solver'); | ||
|
||
describe("Sudoku one", function() { | ||
it("should be solved", function() { | ||
var unsolved_sudoku = "___26_7_168__7__9_19___45__82_1___4___46_29___5___3_28__93___74_4__5__367_3_18___"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a small inconsistency. Using linter and formatter may help you with these. |
||
var solved = "435269781682571493197834562826195347374682915951743628519326874248957136763418259"; | ||
chai.assert.equal(solver.solve(unsolved_sudoku), solved); | ||
}); | ||
it("should return unsolvabe sudoku", function() { | ||
const unsolved_sudoku = "___21_7_168__7__9_19___45__82_1___4___46_29___5___3_28__93___74_4__5__367_3_18___"; | ||
const solved = "Unsolvable sudoku"; | ||
chai.assert.equal(solver.solve(unsolved_sudoku), solved); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
"use strict"; | ||
|
||
const chai = require("chai"); | ||
require("mocha"); | ||
const parser = require("../src/sudokuParser"); | ||
|
||
describe("Sudoku string parser", () => { | ||
const n = null; | ||
const unparsedSudoku = "___26_7_168__7__9_19___45__82_1___4___46_29___5___3_28__93___74_4__5__367_3_18___"; | ||
const parsed = [ | ||
[n, n, n, 2, 6, n, 7, n, 1], | ||
[6, 8, n, n, 7, n, n, 9, n], | ||
[1, 9, n, n, n, 4, 5, n, n], | ||
[8, 2, n, 1, n, n, n, 4, n], | ||
[n, n, 4, 6, n, 2, 9, n, n], | ||
[n, 5, n, n, n, 3, n, 2, 8], | ||
[n, n, 9, 3, n, n, n, 7, 4], | ||
[n, 4, n, n, 5, n, n, 3, 6], | ||
[7, n, 3, n, 1, 8, n, n, n] | ||
]; | ||
|
||
it("Should return matrix from provided string", () => { | ||
chai.assert.deepEqual(parser.parseToArray(unparsedSudoku), parsed); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You missed unit tests for this function.