From 817166c1c3c6194c291219c91dd2478b7f762f1b Mon Sep 17 00:00:00 2001 From: icecream17 Date: Sat, 24 Aug 2024 21:01:06 +0000 Subject: [PATCH] fix ci bug, convert strategy data into table --- .github/workflows/build.yml | 2 +- Strategies.md | 76 +++++++++-------------- Todo | 11 ++++ src/Api/Spaces/PureSudoku.ts | 5 ++ src/Api/Strategies/timeStrategies.test.ts | 44 ++++++------- src/Api/Strategies/twoStringKite.ts | 1 - 6 files changed, 71 insertions(+), 68 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4e2463c..1cceff3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: 'lts' + node-version: 'lts/*' cache: yarn - run: yarn install diff --git a/Strategies.md b/Strategies.md index 8f207f95..263e8662 100644 --- a/Strategies.md +++ b/Strategies.md @@ -390,57 +390,43 @@ About that XY Loop stuff... yeah, XY Chain is special enough to get it's own fun ## Strategy speed -Here's a list of a bunch of strategies, ranked in order of how fast they currently are in my solver, and accompanied with some educated guesses on why they're slow/fast. +Here's a list of a bunch of strategies, ranked in order of how fast they currently are in my solver, and accompanied with some educated guesses on why they're slow/fast. Note that _a strategy is only tried if the previous strategies fail_. "Has dual" just notes that it can happen twice with an almost similar setup (e.g. same base or something) -I'm as surprised with the results as you are - this is definitely not how easy it is for humans - -Ranking is ln random sudokus proccessed per ms. -So strategies that error or finish early get an advantage. +Ironically, some of the "easiest" strategies for humans, like intersection removal, are the hardest so far. +That's mostly because I haven't gotten to the actually complicated strategies. Some easy strategies find all instances within a sudoku = much slower -Tries: 2 - -- [x] Y wing / XY wing / Bent triple (11.71 - 11.77) - - [] Has dual (multi coloring). There's also 3D medusa - - [] This limited form is a subset of X chains -- [x] XY Loop (11.63 - 11.76) -- [x] Update candidates (11.62 - 11.76) - - n^3 (n for each cell, n for each affects(cell), n for checking if affects(cell) contains that solved candidate) -- [x] XYZ Wing (11.64 - 11.73) -- [x] XY Chain (11.64 - 11.71) -- [x] Hidden singles (11.28 - 11.46) - - n^3 (n for each candidate, n for each group, n for each cell in group (checking single)) - - Easier than update candidates because humans - - Despite this, hidden singles doesn't work if the candidates are not updated -- [x] Check for solved (11.26 - 11.36) - - n - - But this also calls "checkValidity" which is about n^3 -- [x] Skyscraper (Subset of wing/coloring) (10.81 - 10.92) - - 2 lines - 1 line = extra - - n^6 ({fish note} - but instead I spend n^3 * {n counting pend lines, n^3 to get attacks(cell of line), n^3 per attacks to get shared} = n^6) -- [x] X wing (really a fish) (10.64 - 10.84) - - 2 lines have a candidate in only 2 crosslines - - n^6 (see {fish note}) - - I have no idea why skyscraper would be faster -- [x] Swordfish (10.17 - 10.33) - - 3 lines have a candidate in only 3 crosslines - - n^6 (see {fish note}) -- [x] Hidden pairs, triples, and quads (10.01 - 10.26) - - N candidates are in N cells (which all see each other) - - n^5 (Pretty much the same as the non hidden strategy.) -- [x] Pairs, triples, and quads (9.51 - 9.61) - - N cells have N candidates (and all see each other) - - n^5 (I have no idea how I did this. My code is quite big) -- [x] Intersection Removal (9.32 - 9.41) - - All candidates in a box see OR All candidates in a line see - - n^4 (n for each candidate, n^2 for box + line, n for each cell in (box - line) or (line - box)) -- [x] Jellyfish (8.99 - 9.11) - - 4 lines have a candidate in only 4 crosslines - - n^6 (see {fish note}) -- [x] Two minus one lines (8.14 - 8.15) +Experimental evidence suggests there are generally only 1 to 2 digits of accuracy for really fast strategies, +but more accuracy for less fast strategies. I tried to estimate digits of accuracy by `log10(time / stdev)`, +but take it with a pinch of salt. + +n is generally 9, so constants are rather important. + +Tries: 1 + +| Strategy | Usefulness | Time^-1 | Estimated digits of accuracy | Complexity | Explanation | +|---------------------------|:------------:|:-------:|:----------------------------:|:------------:|---------------------------------------------------------------------------------------------------------------------------------------------| +| Check for solved | 819543/25790 | 22.2519 | 4.15 | n^3 | n, but it includes "checkValidity" with is n^3 | +| Update candidates | 499816/24325 | 56.8341 | 4.30 | 3n^4 | n^2 cells, 3n affects(cell), n in cell | +| Hidden singles | 17844/12399 | 46.7887 | 4.02 | 3n^3 | Avoids the "n" because the target is a single cell, and instead of deleting candidates one by one, just set the cell to a single candidate. | +| Intersection removal | 8506/7059 | 1.3822 | 3.95 | 4(n^4-n^3.5) | n candidates, n\*2n box*line, 2(n-sqrt(n)) differences (probably did too much optimization here) | +| Pairs, triples, and quads | 12808/4264 | 1.9241 | 3.73 | | | +| Hidden "" | 236/2543 | 3.4062 | 3.45 | | | +| X wing (fish) | 82/2348 | 7.5987 | 3.43 | n^6 | wing note | +| Swordfish | 42/2282 | 3.1475 | 3.41 | n^6 | wing note | +| Jellyfish | 3/2259 | 1.3203 | 3.18 | n^6 | wing note | +| Skyscraper | 205/2257 | 5.1063 | 3.30 | | | +| 2 string kite | 119/2053 | 20.53 | 3.33 | | | +| Y wing | 102/1935 | 46.0714 | 3.30 | | | +| 2-1 lines | 559/1834 | 0.2586 | 3.31 | | | +| W wing | 152/1337 | 8.6258 | 3.17 | | | +| XYZ wing | 72/1192 | 30.5641 | 2.91 | | | +| Pair covers group | 35/1120 | 3.5668 | 3.05 | | | +| XY Loop | 30/1092 | 9.2542 | 2.24 | | | +| XY Chain | 93/1062 | 7.4266 | 2.37 | | | > {wing note}: Instead of nesting loops for each line in a wing, I do the following: > diff --git a/Todo b/Todo index b26b66dd..77832591 100644 --- a/Todo +++ b/Todo @@ -16,6 +16,17 @@ [ ] Exocet [ ] Wow +### Speed + +Premature optimization is bad, but this is the most exciting part of the code. + +[ ] Generalize strategies with a better API +[ ] Switch ordering of loops: I think having the candidate loop be in the inner loop is much faster +[ ] Hidden pairs, triples, and quads is much faster than pairs, triples, and quads +[ ] Make XY Loop and XY Chain be able to short circuit (i.e. not include the starting cell). + This would allow us to skip cells that are already connected, bounding the strategies to n^4 + The strategies are already really fast, so do some testing first. + ## Code [ ] Migrate undo functionality completely to the solver diff --git a/src/Api/Spaces/PureSudoku.ts b/src/Api/Spaces/PureSudoku.ts index eb3c0184..b78dc3e5 100644 --- a/src/Api/Spaces/PureSudoku.ts +++ b/src/Api/Spaces/PureSudoku.ts @@ -219,6 +219,11 @@ export default class PureSudoku { return data.slice(startRow, startRow + 3).flatMap(row => row.slice(startColumn, startColumn + 3)) } + /** + * A group is a set of maximally mutually exclusive set of cells. + * + * Currently, we assume this is just the rows, columns, and boxes, but this will be changed later on. + */ getGroups() { const groups = [] const cellData = this.data.map((row, indexOfRow) => diff --git a/src/Api/Strategies/timeStrategies.test.ts b/src/Api/Strategies/timeStrategies.test.ts index b995b25d..cdcf372d 100644 --- a/src/Api/Strategies/timeStrategies.test.ts +++ b/src/Api/Strategies/timeStrategies.test.ts @@ -19,9 +19,7 @@ function main () { processed: number finds: number errors: number - totalspeed: number - totaldeviation: number - timetaken: number + speeds: number[] }> let done = 0 @@ -40,15 +38,17 @@ function main () { processed: 0, finds: 0, errors: 0, - get totalspeed() {return this.processed / this.timetaken}, - totaldeviation: 0, - timetaken: 0, + speeds: [], } const start = Date.now() // Do strategy // @ts-expect-error - bruh const {successcount = 0} = Strategy(sudoku, { solved: 0 }) + const timetaken = Date.now() - start + + // Update speed + results[Strategy.name].speeds.push(timetaken) // Update process / error let stratSuccess = false @@ -63,7 +63,7 @@ function main () { if (successcount > 0) { const isDone = sudoku.data.every(row => row.every(cell => cell.length === 1)) if (isDone) { - console.log('solved ' + i) + // console.log('solved ' + i) solved.add(i) stratSuccess = true } else { @@ -77,16 +77,6 @@ function main () { } } - // Update speed - const oldtotalspeed = results[Strategy.name].totalspeed - const timetaken = Date.now() - start - results[Strategy.name].timetaken += timetaken - results[Strategy.name].totaldeviation += Math.abs(oldtotalspeed - results[Strategy.name].totalspeed) - // const averagedeviation = results[Strategy.name].totaldeviation / results[Strategy.name].processed - // if (averagedeviation / results[Strategy.name].processed < 0.0001 && timetaken > 100 && results[Strategy.name].processed > 100) { - // break - // } - if (done % 0x1000 === 0) { // unless there's a bug, done is at most 1465*729 = 1067985 // In practice, it currently finishes at (solved: 495, done: 122941) @@ -107,10 +97,22 @@ function main () { } } - console.log(solved.size, done) - - for (const key in results) { // @ts-ignore intentional - results[key].ts = results[key].totalspeed + console.log(`Solved ${solved.size} out of ${mtBoards.length} (~${100 * solved.size / mtBoards.length}%)`, done) + + for (const key in results) { + // @ts-expect-error -- intentional + results[key].ts = results[key].speeds.reduce((a, b) => a + b) + // @ts-expect-error -- intentional + results[key].stdev = results[key].speeds.map(a => (results[key].ts / results[key].processed - a) ** 2).reduce((a, b) => a + b) / results[key].processed + // @ts-expect-error -- intentional + results[key].accuracy = Math.log10(results[key].ts / results[key].stdev) + // @ts-expect-error -- intentional + results[key].hertz = results[key].processed / results[key].ts + + // @ts-expect-error + delete results[key].ts + // @ts-expect-error + delete results[key].speeds } console.log(results) diff --git a/src/Api/Strategies/twoStringKite.ts b/src/Api/Strategies/twoStringKite.ts index 88e8c976..4db44e04 100644 --- a/src/Api/Strategies/twoStringKite.ts +++ b/src/Api/Strategies/twoStringKite.ts @@ -16,7 +16,6 @@ function check(cell1: CellID, cell2: CellID, candidate: SudokuDigits, candLocati const sameRowAsCell1 = candLocations.row[cell1.row] const sameColAsCell2 = candLocations.column[cell2.column] if (sameRowAsCell1.size === 2 && sameColAsCell2.size === 2) { - // Looks big nesting but not really for (const cell1B of sameRowAsCell1) { if (cell1B !== cell1) { for (const cell2B of sameColAsCell2) {