Here is my attempt at Weekend Challenge #3.
Out of scope:
- It only will do 9*9
- It will stop at the first solution
- Could have been smarter at re-using the cache when recursively calling
solveGrid
- I ignored OO as this is not an app, but a simple script
In scope:
Bitflags
& cache for pencil marks
var grid = "...84...9\n" +
"..1.....5\n" +
"8...2146.\n" +
"7.8....9.\n" +
".........\n" +
".5....3.1\n" +
".2491...7\n" +
"9.....5..\n" +
"3...84...";
var N = 3, //Changing N wont do much, solvedValues would have to be updated as well
impossible = 511, //parseInt("111111111",2)
solvedValues =
{
510 : 1, //parseInt("111111110",2)
509 : 2, //parseInt("111111101",2)
507 : 3, //parseInt("111111011",2)
503 : 4, //parseInt("111110111",2)
495 : 5, //parseInt("111101111",2)
479 : 6, //parseInt("111011111",2)
447 : 7, //parseInt("110111111",2)
383 : 8, //parseInt("101111111",2)
255 : 9 //parseInt("011111111",2)
};
/* Create a grid array from a string */
function createGrid( s )
{
return s.split("\n").map( function(v)
{
return v.split("").map( function(v)
{ //Each cell is an array [value, pencilmarks as binary bitflag ]
return [v=="."?0:v*1,0]; //Dots or 0 are accepted as blanks
});
});
}
/* Create a string from a grid array */
function createString( grid )
{
return grid.map( function(row){ return row.map( function(v){ return v[0]; } ).join(""); } ).join("\n");
}
/* Determine which 3*3 zone a given location belongs to */
function deriveZone( x , y )
{
return Math.floor( x / N ) * N + Math.floor( y / N );
}
/* Create a cache, to make addToCache cleaner, I preset the values to 0 */
function Cache()
{
this.rows = []; /* What numbers are already used in a given row */
this.cols = []; /* What numbers are already used in a given column */
this.zones = []; /* What numbers are already used in a given zone */
for( var i = 0 ; i < N*N ; i++ )
{
this.rows.push(0);
this.cols.push(0);
this.zones.push(0);
}
}
/* Add a data point to the cache */
function addToCache( cache , x , y , value )
{
value--; // Value 1 would be shifting 0 times to the right etc.
cache.rows[x] |= 1 << value;
cache.cols[y] |= 1 << value;
cache.zones[deriveZone( x , y )] |= 1 << value;
}
/* Collect a cache of all numbers, assign them to their respective rows and columns */
function buildGridCache( grid )
{
var cache = new Cache(), x, y ,value;
for( x = 0 ; x < N*N ; x++ )
for( y = 0 ; y < N*N ; y++ )
if( value = grid[x][y][0] )
addToCache( cache, x , y , value );
return cache;
}
/* Cache what we know, analyze each cell in the grid, see if anything comes up */
function solveObvious( grid , cache)
{
var redo, x, y;
if( !cache )
cache = buildGridCache( grid );
for( x = 0 ; x < N*N ; x++ )
for( y = 0 ; y < N*N ; y++ )
if( !grid[x][y][0] )
{
grid[x][y][1] |= cache.rows[x] | cache.cols[y] | cache.zones[ deriveZone(x,y) ];
grid[x][y][0] = solvedValues[ grid[x][y][1] ] || 0;
if( grid[x][y][0] )
{
addToCache( cache , x , y , grid[x][y][0] );
redo = true;
}
else if( grid[x][y][1] == impossible )
return false;
}
if(redo)
solveObvious( grid , cache );
return true;
}
/* Is there a cell that still has value 0 ? */
function findUnsolved( grid )
{
var x, y ;
for( x = 0 ; x < N*N ; x++ )
for( y = 0 ; y < N*N ; y++ )
if(!grid[x][y][0] )
return grid[x][y];
}
/* Solve the grid, if solving all obvious cells is not enough, try recursive guessing */
function solveGrid( grid )
{
var possible = solveObvious( grid ),
unsolved = findUnsolved( grid ),
newGrid, i;
if( possible && !unsolved )
return grid;
if( unsolved )
for( i = 0 ; i < N*N ; i++ )
if( unsolved[1] != ( unsolved[1] |= 1<<i ) && ( unsolved[0] = i+1 ) )
if( newGrid = solveGrid( JSON.parse( JSON.stringify( grid ) ) ) )
return newGrid;
return false;
}
/* Parse a grid string, return a grid string if the grid is solved */
function solve( s )
{
var grid = solveGrid( createGrid( s ) );
return grid?createString( grid ):"The puzzle is unsolvable: it has no solution";
}
/* Test case */
console.log( solve( grid ) );
{value: ..., bitmask: ...}
for the individual cells, just for readability \$\endgroup\$