From 383c15794a517a4e6dc0b7364edac1f7056cee82 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 12 Apr 2013 16:28:55 +0000 Subject: [PATCH] Patch from Rogier Goossens to speed up the Pattern solver. do_recurse() now prunes early whenever it encounters a branch of the search tree inconsistent with existing grid data (rather than the previous naive approach of proceeding to enumerate all possibilities anyway and then ruling them out one by one); do_recurse also tries to split the row up into independent sections where possible; finally the main solver loop (all three copies of which have now been factored out into a new solve_puzzle function), instead of simply looping round and round over all the rows and columns, heuristically looks at the ones most changed since the last time deduction was attempted on them, on the basis that that will probably yield the most information the fastest. [originally from svn r9828] --- LICENCE | 2 +- pattern.c | 338 +++++++++++++++++++++++++++++++++++----------------- puzzles.but | 4 +- 3 files changed, 229 insertions(+), 115 deletions(-) diff --git a/LICENCE b/LICENCE index dc1b67e..a34c0f1 100644 --- a/LICENCE +++ b/LICENCE @@ -2,7 +2,7 @@ This software is copyright (c) 2004-2012 Simon Tatham. Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd -Schmidt, Steffen Bauer and Lennard Sprong. +Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/pattern.c b/pattern.c index 25cdd85..300a1b3 100644 --- a/pattern.c +++ b/pattern.c @@ -344,35 +344,76 @@ static int compute_rowdata(int *ret, unsigned char *start, int len, int step) int verbose = FALSE; #endif -static void do_recurse(unsigned char *known, unsigned char *deduced, - unsigned char *row, int *data, int len, +static int do_recurse(unsigned char *known, unsigned char *deduced, + unsigned char *row, + unsigned char *minpos_done, unsigned char *maxpos_done, + unsigned char *minpos_ok, unsigned char *maxpos_ok, + int *data, int len, int freespace, int ndone, int lowest) { int i, j, k; + + /* This algorithm basically tries all possible ways the given rows of + * black blocks can be laid out in the row/column being examined. + * Special care is taken to avoid checking the tail of a row/column + * if the same conditions have already been checked during this recursion + * The algorithm also takes care to cut its losses as soon as an + * invalid (partial) solution is detected. + */ if (data[ndone]) { + if (lowest >= minpos_done[ndone] && lowest <= maxpos_done[ndone]) { + if (lowest >= minpos_ok[ndone] && lowest <= maxpos_ok[ndone]) { + for (i=0; i= minpos_ok[ndone] && lowest <= maxpos_ok[ndone]; + } else { + if (lowest < minpos_done[ndone]) minpos_done[ndone] = lowest; + if (lowest > maxpos_done[ndone]) maxpos_done[ndone] = lowest; + } for (i=0; i<=freespace; i++) { j = lowest; - for (k=0; k maxpos_ok[ndone]) maxpos_ok[ndone] = lowest + i; + if (lowest + i > maxpos_done[ndone]) maxpos_done[ndone] = lowest + i; + } + next_iter: + j++; } + return lowest >= minpos_ok[ndone] && lowest <= maxpos_ok[ndone]; } else { - for (i=lowest; i= 0 && known[i] == DOT; i--) + freespace--; + + do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok, maxpos_ok, data, len, freespace, 0, 0); - do_recurse(known, deduced, row, data, len, freespace, 0, 0); done_any = FALSE; for (i=0; irowdata + state->rowsize*(w+i), max*sizeof(int)); + rowdata[state->rowlen[w+i]] = 0; + } else { + rowdata[compute_rowdata(rowdata, grid+i*w, w, 1)] = 0; + } + for (j=0, freespace=w+1; rowdata[j]; j++) freespace -= rowdata[j] + 1; + for (j=0, changed_h[i]=0; rowdata[j]; j++) + if (rowdata[j] > freespace) + changed_h[i] += rowdata[j] - freespace; + } + for (i=0,max_h=0; i max_h) + max_h = changed_h[i]; + for (i=0; irowdata + state->rowsize*i, max*sizeof(int)); + rowdata[state->rowlen[i]] = 0; + } else { + rowdata[compute_rowdata(rowdata, grid+i, h, w)] = 0; + } + for (j=0, freespace=h+1; rowdata[j]; j++) freespace -= rowdata[j] + 1; + for (j=0, changed_w[i]=0; rowdata[j]; j++) + if (rowdata[j] > freespace) + changed_w[i] += rowdata[j] - freespace; + } + for (i=0,max_w=0; i max_w) + max_w = changed_w[i]; + + /* Solve the puzzle. + * Process rows/columns individually. Deductions involving more than one + * row and/or column at a time are not supported. + * Take care to only process rows/columns which have been changed since they + * were previously processed. + * Also, prioritize rows/columns which have had the most changes since their + * previous processing, as they promise the greatest benefit. + * Extremely rectangular grids (e.g. 10x20, 15x40, etc.) are not treated specially. + */ + do { + for (; max_h && max_h >= max_w; max_h--) { + for (i=0; i= max_h) { + if (state) { + memcpy(rowdata, state->rowdata + state->rowsize*(w+i), max*sizeof(int)); + rowdata[state->rowlen[w+i]] = 0; + } else { + rowdata[compute_rowdata(rowdata, grid+i*w, w, 1)] = 0; + } + do_row(workspace, workspace+max, workspace+2*max, + workspace+3*max, workspace+4*max, + workspace+5*max, workspace+6*max, + matrix+i*w, w, 1, rowdata, changed_w +#ifdef STANDALONE_SOLVER + , "row", i+1, cluewid +#endif + ); + changed_h[i] = 0; + } + } + for (i=0,max_w=0; i max_w) + max_w = changed_w[i]; + } + for (; max_w && max_w >= max_h; max_w--) { + for (i=0; i= max_w) { + if (state) { + memcpy(rowdata, state->rowdata + state->rowsize*i, max*sizeof(int)); + rowdata[state->rowlen[i]] = 0; + } else { + rowdata[compute_rowdata(rowdata, grid+i, h, w)] = 0; + } + do_row(workspace, workspace+max, workspace+2*max, + workspace+3*max, workspace+4*max, + workspace+5*max, workspace+6*max, + matrix+i, h, w, rowdata, changed_h +#ifdef STANDALONE_SOLVER + , "col", i+1, cluewid +#endif + ); + changed_w[i] = 0; + } + } + for (i=0,max_h=0; i max_h) + max_h = changed_h[i]; + } + } while (max_h>0 || max_w>0); + + ok = TRUE; + for (i=0; iw, h = state->h; int i; char *ret; - int done_any, max; + int max, ok; unsigned char *workspace; + unsigned int *changed_h, *changed_w; int *rowdata; /* @@ -719,47 +876,25 @@ static char *solve_game(game_state *state, game_state *currstate, if (ai) return dupstr(ai); - matrix = snewn(w*h, unsigned char); max = max(w, h); - workspace = snewn(max*3, unsigned char); + matrix = snewn(w*h, unsigned char); + workspace = snewn(max*7, unsigned char); + changed_h = snewn(max+1, unsigned int); + changed_w = snewn(max+1, unsigned int); rowdata = snewn(max+1, int); - memset(matrix, 0, w*h); - - do { - done_any = 0; - for (i=0; irowdata + state->rowsize*(w+i), - max*sizeof(int)); - rowdata[state->rowlen[w+i]] = 0; - done_any |= do_row(workspace, workspace+max, workspace+2*max, - matrix+i*w, w, 1, rowdata -#ifdef STANDALONE_SOLVER - , NULL, 0, 0 /* never do diagnostics here */ -#endif - ); - } - for (i=0; irowdata + state->rowsize*i, max*sizeof(int)); - rowdata[state->rowlen[i]] = 0; - done_any |= do_row(workspace, workspace+max, workspace+2*max, - matrix+i, h, w, rowdata -#ifdef STANDALONE_SOLVER - , NULL, 0, 0 /* never do diagnostics here */ -#endif - ); - } - } while (done_any); + ok = solve_puzzle(state, NULL, w, h, matrix, workspace, + changed_h, changed_w, rowdata, 0); sfree(workspace); + sfree(changed_h); + sfree(changed_w); sfree(rowdata); - for (i = 0; i < w*h; i++) { - if (matrix[i] != BLOCK && matrix[i] != DOT) { - sfree(matrix); - *error = "Solving algorithm cannot complete this puzzle"; - return NULL; - } + if (!ok) { + sfree(matrix); + *error = "Solving algorithm cannot complete this puzzle"; + return NULL; } ret = snewn(w*h+2, char); @@ -1635,17 +1770,18 @@ int main(int argc, char **argv) s = new_game(NULL, p, desc); { - int w = p->w, h = p->h, i, j, done_any, max, cluewid = 0; + int w = p->w, h = p->h, i, j, max, cluewid = 0; unsigned char *matrix, *workspace; + unsigned int *changed_h, *changed_w; int *rowdata; matrix = snewn(w*h, unsigned char); max = max(w, h); - workspace = snewn(max*3, unsigned char); + workspace = snewn(max*7, unsigned char); + changed_h = snewn(max+1, unsigned int); + changed_w = snewn(max+1, unsigned int); rowdata = snewn(max+1, int); - memset(matrix, 0, w*h); - if (verbose) { int thiswid; /* @@ -1662,30 +1798,8 @@ int main(int argc, char **argv) } } - do { - done_any = 0; - for (i=0; irowdata + s->rowsize*(w+i), - max*sizeof(int)); - rowdata[s->rowlen[w+i]] = 0; - done_any |= do_row(workspace, workspace+max, workspace+2*max, - matrix+i*w, w, 1, rowdata -#ifdef STANDALONE_SOLVER - , "row", i+1, cluewid -#endif - ); - } - for (i=0; irowdata + s->rowsize*i, max*sizeof(int)); - rowdata[s->rowlen[i]] = 0; - done_any |= do_row(workspace, workspace+max, workspace+2*max, - matrix+i, h, w, rowdata -#ifdef STANDALONE_SOLVER - , "col", i+1, cluewid -#endif - ); - } - } while (done_any); + solve_puzzle(s, NULL, w, h, matrix, workspace, + changed_h, changed_w, rowdata, cluewid); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { diff --git a/puzzles.but b/puzzles.but index 11a0525..b009949 100644 --- a/puzzles.but +++ b/puzzles.but @@ -3108,8 +3108,8 @@ both the width and height to be even numbers.) This software is \i{copyright} 2004-2012 Simon Tatham. Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas -K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros -Lambrou, Bernd Schmidt, Steffen Bauer and Lennard Sprong. +K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, +Bernd Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files