September 2017 Update

This commit is contained in:
Ingy döt Net 2017-09-23 10:01:46 +02:00
parent bba7bfd280
commit ba8067c3b7
14570 changed files with 153136 additions and 63871 deletions

View file

@ -58,10 +58,14 @@ Write a program to solve puzzles of this ilk,
demonstrating your program by solving the above examples.
Extra credit for other interesting examples.
Related Tasks:
;Related tasks:
* [[A* search algorithm]]
* [[Solve a Holy Knight's tour]]
* [[Knight's tour]]
* [[N-queens problem]]
* [[Solve a Hidato puzzle]]
* [[Solve a Holy Knight's tour]]
* [[Solve a Hopido puzzle]]
* [[Solve the no connection puzzle]]
* [[Knight's tour]]
<br><br>

View file

@ -1,209 +0,0 @@
import std.stdio, std.conv, std.string, std.range, std.array, std.typecons, std.algorithm;
struct {
alias BitSet8 = ubyte; // A set of 8 bits.
alias Cell = uint;
enum : string { unavailableInCell = "#", availableInCell = "." }
enum : Cell { unavailableCell = Cell.max, availableCell = 0 }
this(in string inPuzzle) pure @safe {
const rawPuzzle = inPuzzle.splitLines.map!(row => row.split).array;
assert(!rawPuzzle.empty);
assert(!rawPuzzle[0].empty);
assert(rawPuzzle.all!(row => row.length == rawPuzzle[0].length)); // Is rectangular.
gridWidth = rawPuzzle[0].length;
gridHeight = rawPuzzle.length;
immutable nMaxCells = gridWidth * gridHeight;
grid = new Cell[nMaxCells];
auto knownMutable = new bool[nMaxCells + 1];
uint nAvailableMutable = nMaxCells;
bool[Cell] seenCells; // To avoid duplicate input numbers.
uint i = 0;
foreach (const piece; rawPuzzle.join) {
if (piece == unavailableInCell) {
nAvailableMutable--;
grid[i++] = unavailableCell;
continue;
} else if (piece == availableInCell) {
grid[i] = availableCell;
} else {
immutable cell = piece.to!Cell;
assert(cell > 0 && cell <= nMaxCells);
assert(cell !in seenCells);
seenCells[cell] = true;
knownMutable[cell] = true;
grid[i] = cell;
}
i++;
}
known = knownMutable.idup;
nAvailable = nAvailableMutable;
}
@disable this();
auto solve() pure nothrow @safe @nogc
out(result) {
if (!result.isNull) {
// Can't verify 'result' here because it's const.
// assert(!result.get.join.canFind(availableCell.text));
assert(!grid.canFind(availableCell));
auto values = grid.filter!(c => c != unavailableCell);
auto interval = iota(reduce!min(values.front, values.dropOne),
reduce!max(values.front, values.dropOne) + 1);
assert(values.walkLength == interval.length);
assert(interval.all!(c => values.count(c) == 1)); // Quadratic.
}
} body {
auto result = grid
.map!(c => (c == unavailableCell) ? unavailableInCell : c.text)
.chunks(gridWidth);
alias OutRange = Nullable!(typeof(result));
const start = findStart;
if (start.isNull)
return OutRange();
search(start.r, start.c, start.cell + 1, 1);
if (start.cell > 1) {
immutable direction = -1;
search(start.r, start.c, start.cell + direction, direction);
}
if (grid.any!(c => c == availableCell))
return OutRange();
else
return OutRange(result);
}
private:
bool search(in uint r, in uint c, in Cell cell, in int direction)
pure nothrow @safe @nogc {
if ((cell > nAvailable && direction > 0) || (cell == 0 && direction < 0) ||
(cell == nAvailable && known[cell]))
return true; // One solution found.
immutable neighbors = getNeighbors(r, c);
if (known[cell]) {
foreach (immutable i, immutable rc; shifts) {
if (neighbors & (1u << i)) {
immutable c2 = c + rc[0],
r2 = r + rc[1];
if (grid[r2 * gridWidth + c2] == cell)
if (search(r2, c2, cell + direction, direction))
return true;
}
}
return false;
}
foreach (immutable i, immutable rc; shifts) {
if (neighbors & (1u << i)) {
immutable c2 = c + rc[0],
r2 = r + rc[1],
pos = r2 * gridWidth + c2;
if (grid[pos] == availableCell) {
grid[pos] = cell; // Try.
if (search(r2, c2, cell + direction, direction))
return true;
grid[pos] = availableCell; // Restore.
}
}
}
return false;
}
BitSet8 getNeighbors(in uint r, in uint c) const pure nothrow @safe @nogc {
typeof(return) usable = 0;
foreach (immutable i, immutable rc; shifts) {
immutable c2 = c + rc[0],
r2 = r + rc[1];
if (c2 >= gridWidth || r2 >= gridHeight)
continue;
if (grid[r2 * gridWidth + c2] != unavailableCell)
usable |= (1u << i);
}
return usable;
}
auto findStart() const pure nothrow @safe @nogc {
alias Triple = Tuple!(uint,"r", uint,"c", Cell,"cell");
Nullable!Triple result;
auto cell = Cell.max;
foreach (immutable r; 0 .. gridHeight) {
foreach (immutable c; 0 .. gridWidth) {
immutable pos = gridWidth * r + c;
if (grid[pos] != availableCell &&
grid[pos] != unavailableCell && grid[pos] < cell) {
cell = grid[pos];
result = Triple(r, c, cell);
}
}
}
return result;
}
static immutable int[2][4] shifts = [[0, -1], [0, 1], [-1, 0], [1, 0]];
immutable uint gridWidth, gridHeight;
immutable int nAvailable;
immutable bool[] known; // Given known cells of the puzzle.
Cell[] grid; // Flattened mutable game grid.
}
void main() {
// enum NumbrixPuzzle to catch malformed puzzles at compile-time.
enum puzzle1 = ". . . . . . . . .
. . 46 45 . 55 74 . .
. 38 . . 43 . . 78 .
. 35 . . . . . 71 .
. . 33 . . . 59 . .
. 17 . . . . . 67 .
. 18 . . 11 . . 64 .
. . 24 21 . 1 2 . .
. . . . . . . . .".NumbrixPuzzle;
enum puzzle2 = ". . . . . . . . .
. 11 12 15 18 21 62 61 .
. 6 . . . . . 60 .
. 33 . . . . . 57 .
. 32 . . . . . 56 .
. 37 . 1 . . . 73 .
. 38 . . . . . 72 .
. 43 44 47 48 51 76 77 .
. . . . . . . . .".NumbrixPuzzle;
enum puzzle3 = "17 . . . 11 . . . 59
. 15 . . 6 . . 61 .
. . 3 . . . 63 . .
. . . . 66 . . . .
23 24 . 68 67 78 . 54 55
. . . . 72 . . . .
. . 35 . . . 49 . .
. 29 . . 40 . . 47 .
31 . . . 39 . . . 45".NumbrixPuzzle;
foreach (puzzle; [puzzle1, puzzle2, puzzle3]) {
auto solution = puzzle.solve; // Solved at run-time.
if (solution.isNull)
writeln("No solution found for puzzle.\n");
else
writefln("One solution:\n%(%-(%2s %)\n%)\n", solution);
}
}

View file

@ -1,90 +0,0 @@
global nCells, cMap, best
record Pos(r,c)
procedure main(A)
puzzle := showPuzzle("Input",readPuzzle())
QMouse(puzzle,findStart(puzzle),&null,0)
showPuzzle("Output", solvePuzzle(puzzle)) | write("No solution!")
end
procedure readPuzzle()
# Start with a reduced puzzle space
p := []
nCells := maxCols := 0
every line := !&input do {
put(p,[: gencells(line) :])
maxCols <:= *p[-1]
}
# Now normalize all rows to the same length
every i := 1 to *p do p[i] := [: !p[i] | (|-1\(maxCols - *p[i])) :]
return p
end
procedure gencells(s)
static WS, NWS
initial {
NWS := ~(WS := " \t")
cMap := table() # Map to/from internal model
cMap["_"] := 0; cMap[0] := "_"
}
s ? while not pos(0) do {
w := (tab(many(WS))|"", tab(many(NWS))) | break
w := numeric(\cMap[w]|w)
if -1 ~= w then nCells +:= 1
suspend w
}
end
procedure showPuzzle(label, p)
write(label," with ",nCells," cells:")
every r := !p do {
every c := !r do writes(right((\cMap[c]|c),*nCells+1))
write()
}
return p
end
procedure findStart(p)
if \p[r := !*p][c := !*p[r]] = 1 then return Pos(r,c)
end
procedure solvePuzzle(puzzle)
if path := \best then {
repeat {
loc := path.getLoc()
puzzle[loc.r][loc.c] := path.getVal()
path := \path.getParent() | break
}
return puzzle
}
end
class QMouse(puzzle, loc, parent, val)
method getVal(); return val; end
method getLoc(); return loc; end
method getParent(); return parent; end
method atEnd(); return (nCells = val, puzzle[loc.r,loc.c] = (val|0)); end
method visit(r,c); return (/best, validPos(r,c), Pos(r,c)); end
method validPos(r,c)
v := val+1 # number we're looking for
xv := puzzle[r,c] | fail
if (xv ~= 0) & (xv != v) then fail
if xv = (0|v) then {
ancestor := self
while xl := (ancestor := \ancestor.getParent()).getLoc() do
if (xl.r = r) & (xl.c = c) then fail
return
}
end
initially
val := val+1
if atEnd() then return best := self
QMouse(puzzle, visit(loc.r-1,loc.c) , self, val) # North
QMouse(puzzle, visit(loc.r, loc.c+1), self, val) # East
QMouse(puzzle, visit(loc.r+1,loc.c), self, val) # South
QMouse(puzzle, visit(loc.r, loc.c-1), self, val) # West
end

View file

@ -0,0 +1,98 @@
from sys import stdout
neighbours = [[-1, 0], [0, -1], [1, 0], [0, 1]]
exists = []
lastNumber = 0
wid = 0
hei = 0
def find_next(pa, x, y, z):
for i in range(4):
a = x + neighbours[i][0]
b = y + neighbours[i][1]
if wid > a > -1 and hei > b > -1:
if pa[a][b] == z:
return a, b
return -1, -1
def find_solution(pa, x, y, z):
if z > lastNumber:
return 1
if exists[z] == 1:
s = find_next(pa, x, y, z)
if s[0] < 0:
return 0
return find_solution(pa, s[0], s[1], z + 1)
for i in range(4):
a = x + neighbours[i][0]
b = y + neighbours[i][1]
if wid > a > -1 and hei > b > -1:
if pa[a][b] == 0:
pa[a][b] = z
r = find_solution(pa, a, b, z + 1)
if r == 1:
return 1
pa[a][b] = 0
return 0
def solve(pz, w, h):
global lastNumber, wid, hei, exists
lastNumber = w * h
wid = w
hei = h
exists = [0 for j in range(lastNumber + 1)]
pa = [[0 for j in range(h)] for i in range(w)]
st = pz.split()
idx = 0
for j in range(h):
for i in range(w):
if st[idx] == ".":
idx += 1
else:
pa[i][j] = int(st[idx])
exists[pa[i][j]] = 1
idx += 1
x = 0
y = 0
t = w * h + 1
for j in range(h):
for i in range(w):
if pa[i][j] != 0 and pa[i][j] < t:
t = pa[i][j]
x = i
y = j
return find_solution(pa, x, y, t + 1), pa
def show_result(r):
if r[0] == 1:
for j in range(hei):
for i in range(wid):
stdout.write(" {:0{}d}".format(r[1][i][j], 2))
print()
else:
stdout.write("No Solution!\n")
print()
r = solve(". . . . . . . . . . . 46 45 . 55 74 . . . 38 . . 43 . . 78 . . 35 . . . . . 71 . . . 33 . . . 59 . . . 17"
" . . . . . 67 . . 18 . . 11 . . 64 . . . 24 21 . 1 2 . . . . . . . . . . .", 9, 9)
show_result(r)
r = solve(". . . . . . . . . . 11 12 15 18 21 62 61 . . 6 . . . . . 60 . . 33 . . . . . 57 . . 32 . . . . . 56 . . 37"
" . 1 . . . 73 . . 38 . . . . . 72 . . 43 44 47 48 51 76 77 . . . . . . . . . .", 9, 9)
show_result(r)
r = solve("17 . . . 11 . . . 59 . 15 . . 6 . . 61 . . . 3 . . . 63 . . . . . . 66 . . . . 23 24 . 68 67 78 . 54 55"
" . . . . 72 . . . . . . 35 . . . 49 . . . 29 . . 40 . . 47 . 31 . . . 39 . . . 45", 9, 9)
show_result(r)

View file

@ -0,0 +1,129 @@
/// @Author: Alexandre Felipe (o.alexandre.felipe@gmail.com)
/// @Date: 2017-May-10
///
//////////////////////////////////////////////////////////////////////////////
/// NumbrixSolver ///
/// Solve the puzzle, by using system verilog randomization engine ///
//////////////////////////////////////////////////////////////////////////////
class NumbrixSolver;
rand int solvedBoard[][];
int fixedBoard[][];
int numCells;
////////////////////////////////////////////////////////////////////////////
/// Dynamically resize the board accordingly to the size of the reference///
/// board ///
////////////////////////////////////////////////////////////////////////////
constraint height {
solvedBoard.size == fixedBoard.size;
}
constraint width {
foreach(solvedBoard[i]) solvedBoard[i].size == fixedBoard[i].size;
}
////////////////////////////////////////////////////////////////////////////
/// Fix the positions defined in the input board ///
////////////////////////////////////////////////////////////////////////////
constraint fixed {
foreach(solvedBoard[i]) foreach(solvedBoard[i][j])
if(fixedBoard[i][j] != 0)solvedBoard[i][j] == fixedBoard[i][j];
}
////////////////////////////////////////////////////////////////////////////
/// Ensures that the whole board is filled from the number with numbers ///
/// 1,2,3,...,numCells ///
////////////////////////////////////////////////////////////////////////////
constraint range {
foreach(solvedBoard[i])foreach(solvedBoard[i][j])
solvedBoard[i][j] inside {[1:numCells]};
}
////////////////////////////////////////////////////////////////////////////
/// Ensures that there is no repeated number, consequently every number ///
/// is present on the board ///
////////////////////////////////////////////////////////////////////////////
constraint uniqueness {
foreach(solvedBoard[i1]) foreach(solvedBoard[i1][j1])
foreach(solvedBoard[i2]) foreach(solvedBoard[i2][j2])
if((i1 != i2) || (j1 != j2)) solvedBoard[i1][j1] != solvedBoard[i2][j2];
}
////////////////////////////////////////////////////////////////////////////
/// Ensures that exists one direction connecting the numbers in ///
/// increasing order ///
////////////////////////////////////////////////////////////////////////////
constraint f_seq {
foreach(solvedBoard[i])foreach(solvedBoard[i][j])
(solvedBoard[i][j] == (numCells)) ||
(solvedBoard[(i < solvedBoard.size-1) ? (i+1): i][j] ==
solvedBoard[i][j]+1) ||
(solvedBoard[i][(j < solvedBoard[i].size - 1) ? j+1: j] ==
solvedBoard[i][j]+1) ||
(solvedBoard[(i > 0) ? i-1: i][j] ==
solvedBoard[i][j]+1) ||
(solvedBoard[i][(j > 0)? j-1:j] ==
solvedBoard[i][j]+1);
}
function void pre_randomize();
// the multiplication is not supported in the constraints
numCells = fixedBoard.size * fixedBoard[0].size;
endfunction
function void printSolvedBoard();
foreach(solvedBoard[i]) begin
foreach(solvedBoard[j]) begin
$write("%4d", solvedBoard[i][j]);
end
$display("");
end
endfunction
endclass
//////////////////////////////////////////////////////////////////////////////
/// SolveNumBrix: A program demonstrating how to use NumbrixSolver class ///
//////////////////////////////////////////////////////////////////////////////
program SolveNumbrix;
NumbrixSolver board;
initial begin
board = new;
board.fixedBoard = '{
'{0, 0, 0, 0, 0, 0, 0, 0, 0},
'{0, 0, 46, 45, 0, 55, 74, 0, 0},
'{0, 38, 0, 0, 43, 0, 0, 78, 0},
'{0, 35, 0, 0, 0, 0, 0, 71, 0},
'{0, 0, 33, 0, 0, 0, 59, 0, 0},
'{0, 17, 0, 0, 0, 0, 0, 67, 0},
'{0, 18, 0, 0, 11, 0, 0, 64, 0},
'{0, 0, 24, 21, 0, 1, 2, 0, 0},
'{0, 0, 0, 0, 0, 0, 0, 0, 0}};
if(board.randomize()) begin
$display("Solution for the Example 1");
board.printSolvedBoard();
end
else begin
$display("Failed to solve Example 1");
end
board.fixedBoard = '{
{0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 11, 12, 15, 18, 21, 62, 61, 0},
{0, 6, 0, 0, 0, 0, 0, 60, 0},
{0, 33, 0, 0, 0, 0, 0, 57, 0},
{0, 32, 0, 0, 0, 0, 0, 56, 0},
{0, 37, 0, 1, 0, 0, 0, 73, 0},
{0, 38, 0, 0, 0, 0, 0, 72, 0},
{0, 43, 44, 47, 48, 51, 76, 77, 0},
'{0, 0, 0, 0, 0, 0, 0, 0, 0}};
if(board.randomize()) begin
$display("Solution for the Example 2");
board.printSolvedBoard();
end
else begin
$display("Failed to solve Example 2");
end
$finish;
end
endprogram

View file

@ -0,0 +1,58 @@
// Solve Hidato/Hopido/Numbrix puzzles
class Puzzle{ // hold info concerning this puzzle
var board, nrows,ncols, cells,
start, // (r,c) where 1 is located, Void if no 1
terminated, // if board holds highest numbered cell
given, // all the pre-loaded cells
adj, // a list of (r,c) that are valid next cells
;
fcn print_board{
d:=Dictionary(-1," ", 0,"__");
foreach r in (board){
r.pump(String,'wrap(c){ "%2s ".fmt(d.find(c,c)) }).println();
}
}
fcn init(s,adjacent){
adj=adjacent;
lines:=s.split("\n");
ncols,nrows=lines[0].split().len(),lines.len();
board=nrows.pump(List(), ncols.pump(List(),-1).copy);
given,start=List(),Void;
cells,terminated=0,True;
foreach r,row in (lines.enumerate()){
foreach c,cell in (row.split().enumerate()){
if(cell=="X") continue; // X == not in play, leave at -1
cells+=1;
val:=cell.toInt();
board[r][c]=val;
given.append(val);
if(val==1) start=T(r,c);
}
}
println("Number of cells = ",cells);
if(not given.holds(cells)){ given.append(cells); terminated=False; }
given=given.filter().sort();
}
fcn solve{ //-->Bool
if(start) return(_solve(start.xplode()));
foreach r,c in (nrows,ncols){
if(board[r][c]==0 and _solve(r,c)) return(True);
}
False
}
fcn [private] _solve(r,c,n=1, next=0){
if(n>given[-1]) return(True);
if(not ( (0<=r<nrows) and (0<=c<ncols) )) return(False);
if(board[r][c] and board[r][c]!=n) return(False);
if(terminated and board[r][c]==0 and given[next]==n) return(False);
back:=0;
if(board[r][c]==n){ next+=1; back=n; }
board[r][c]=n;
foreach i,j in (adj){ if(self.fcn(r+i,c+j,n+1, next)) return(True) }
board[r][c]=back;
False
}
} // Puzzle

View file

@ -0,0 +1,37 @@
hi1:= // 0==empty cell, X==not a cell
#<<<
"0 0 0 0 0 0 0 0 0
0 0 46 45 0 55 74 0 0
0 38 0 0 43 0 0 78 0
0 35 0 0 0 0 0 71 0
0 0 33 0 0 0 59 0 0
0 17 0 0 0 0 0 67 0
0 18 0 0 11 0 0 64 0
0 0 24 21 0 1 2 0 0
0 0 0 0 0 0 0 0 0";
#<<<
hi2:= // 0==empty cell, X==not a cell
#<<<
"0 0 0 0 0 0 0 0 0
0 11 12 15 18 21 62 61 0
0 6 0 0 0 0 0 60 0
0 33 0 0 0 0 0 57 0
0 32 0 0 0 0 0 56 0
0 37 0 1 0 0 0 73 0
0 38 0 0 0 0 0 72 0
0 43 44 47 48 51 76 77 0
0 0 0 0 0 0 0 0 0";
#<<<
adjacent:=T( T(-1,0),
T( 0,-1), T( 0,1),
T( 1,0) );
foreach hi in (T(hi1,hi2)){
puzzle:=Puzzle(hi); puzzle.adjacent=adjacent;
puzzle.print_board();
puzzle.solve();
println();
puzzle.print_board();
println();
}