From 05956930083418f2f2b3a09fce324585ee78f117 Mon Sep 17 00:00:00 2001 From: steven nguyen Date: Fri, 17 Dec 2021 09:51:51 -0800 Subject: [PATCH] General improvements (#151) Note: Somehow I duplicated my commits while trying to squash them?????????????????????????????????????? * groups [skip ci] * getBoxGroup test [skip ci] * slightly optimise test [skip ci] * Use getGroups * fix lint * fix typescript lint * Fix syntax move assertion * remove `candidates` property [skip ci] * Use groups, fix export types * remove candidates * fix types [skip ci] * fix types (2) * remove unused imports [skip ci] * remove unused imports 2 * remove whitespace [skip ci] * fix test * same format as old * Don't differentiate groups unnecessarily * Fix types * more typing fix * even more typing * refactor * fix typescript typing * fix typing again * fix typing 3 * remove unused imports 1 [skip ci] * remove unused imports 2 * make faster, fix bad errors * remove changes * oops fix test too [skip ci] * groups [skip ci] * getBoxGroup test [skip ci] * slightly optimise test [skip ci] * Use getGroups * fix lint * fix typescript lint * Fix syntax move assertion * remove `candidates` property [skip ci] * Use groups, fix export types * remove candidates * fix types [skip ci] * fix types (2) * remove unused imports [skip ci] * remove unused imports 2 * remove whitespace [skip ci] * fix test * same format as old * Don't differentiate groups unnecessarily * Fix types * more typing fix * even more typing * refactor * fix typescript typing * fix typing again * fix typing 3 * remove unused imports 1 [skip ci] * remove unused imports 2 * make faster, fix bad errors * remove changes * oops fix test too [skip ci] * unused import [skip ci] * consistent location [skip ci] * ???? [skip ci] * more consistency [skip ci] --- .gitpod.yml | 3 ++ package.json | 2 +- src/Api/Spaces/PureSudoku.test.ts | 5 +- src/Api/Spaces/PureSudoku.ts | 4 ++ src/Api/Strategies/Strategies.test.tsx | 5 +- .../Strategies/hiddenPairsTriplesAndQuads.ts | 53 +++++++++++++------ src/Api/Strategies/pairsTriplesAndQuads.ts | 22 ++++---- 7 files changed, 61 insertions(+), 33 deletions(-) create mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000..569ac2ae --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,3 @@ +tasks: + - init: yarn install && yarn run build + command: yarn run start \ No newline at end of file diff --git a/package.json b/package.json index d4a2b770..082bb0fd 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "test-with-coverage": "npm test -- --coverage" + "test-with-coverage": "yarn test --coverage" }, "browserslist": { "production": [ diff --git a/src/Api/Spaces/PureSudoku.test.ts b/src/Api/Spaces/PureSudoku.test.ts index 38416ba1..133925a3 100644 --- a/src/Api/Spaces/PureSudoku.test.ts +++ b/src/Api/Spaces/PureSudoku.test.ts @@ -14,10 +14,11 @@ test('it imports', () => { expect(testSudoku.data[4][2]).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]) for (const board of Object.values(BOARDS)) { - if (!testSudoku.import(board).success) { + const importSuccess = testSudoku.import(board).success + if (!importSuccess) { console.debug(board) } - expect(testSudoku.import(board).success).toBe(true) + expect(importSuccess).toBe(true) expect(testSudoku.to81()).toBe(new PureSudoku(board).to81()) } diff --git a/src/Api/Spaces/PureSudoku.ts b/src/Api/Spaces/PureSudoku.ts index 3517c965..30b59382 100644 --- a/src/Api/Spaces/PureSudoku.ts +++ b/src/Api/Spaces/PureSudoku.ts @@ -2,6 +2,10 @@ import { ALL_CANDIDATES, IndexToNine, INDICES_TO_NINE, SudokuDigits, ThreeDimensionalArray } from "../../Types" import { boxAt, CellID, id, to9by9 } from "../Utils" +/** + * Defines base sudoku methods + * Should I move these to utils? + */ export default class PureSudoku { data: ThreeDimensionalArray constructor(representation?: string) { diff --git a/src/Api/Strategies/Strategies.test.tsx b/src/Api/Strategies/Strategies.test.tsx index 5479e056..0049d7d2 100644 --- a/src/Api/Strategies/Strategies.test.tsx +++ b/src/Api/Strategies/Strategies.test.tsx @@ -375,7 +375,7 @@ describe('strategies', () => { }) }) - test('Examples 7, 8, and 9 (should error)', () => { + test('Examples 7, 8, 9, and 10 (should error)', () => { const testSudoku = setupSudoku(` 123...... ...123... @@ -416,6 +416,9 @@ describe('strategies', () => { `) updateCandidates(testSudoku) expect(hiddenPairsTriplesAndQuads(testSudoku).successcount).toBe(SuccessError) + + testSudoku.import('020000700020006700000006700020000700000400089000400089000400009100000080100000089123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789') + expect(hiddenPairsTriplesAndQuads(testSudoku).successcount).toBe(SuccessError) }) }); diff --git a/src/Api/Strategies/hiddenPairsTriplesAndQuads.ts b/src/Api/Strategies/hiddenPairsTriplesAndQuads.ts index ba950e88..eca7fda6 100644 --- a/src/Api/Strategies/hiddenPairsTriplesAndQuads.ts +++ b/src/Api/Strategies/hiddenPairsTriplesAndQuads.ts @@ -3,12 +3,12 @@ import { convertArrayToEnglishList } from "../../utils"; import PureSudoku from "../Spaces/PureSudoku"; import { SuccessError } from "../Types"; import { algebraic, CellID, getIDFromIndexWithinBox, id, removeFromArray } from "../Utils"; -import { CellInfo, colorConjugate, combinations, _CellInfoList } from "./pairsTriplesAndQuads"; +import { CellInfo, colorConjugate, combinations, CellGroup } from "./pairsTriplesAndQuads"; /** * Returns an array of all the cells which contain at least one of the candidates */ -function getConjugateFromCandidates (cells: _CellInfoList, candidates: SudokuDigits[]) { +function getConjugateFromCandidates (cells: CellGroup, candidates: SudokuDigits[]) { return cells.filter(cell => candidates.some(candidate => cell.candidates.includes(candidate)) ) @@ -35,7 +35,7 @@ function __errorHandling (candidatesOfConjugate: SudokuDigits[], conjugate: Cell } } -function __filterPossibleCandidates (groupCopy: SudokuDigits[][], maxSize: number, possibleCandidates: Set<1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9>) { +function __filterPossibleCandidates (groupCopy: SudokuDigits[][], maxSize: number, possibleCandidates: Set) { function removeCandidate (candidate: SudokuDigits) { possibleCandidates.delete(candidate) @@ -57,11 +57,13 @@ function __filterPossibleCandidates (groupCopy: SudokuDigits[][], maxSize: numbe for (const candidate of possibleCandidates) { if (occurances[candidate] > maxSize) { removeCandidate(candidate) + } else if (occurances[candidate] === 0) { + return `There is nowhere to put ${candidate}!` as const } } // b. Remove candidates that are alone in a cell - // Also maxSize become possibleCandidates.size + // maxSize is now possibleCandidates.size let keepGoing = true while (keepGoing) { keepGoing = false @@ -106,17 +108,19 @@ function findHiddenConjugatesOfGroup( indexToPosition: (index: IndexToNine) => CellID, maxSize = 4 as 2 | 3 | 4 ) { - // Copy the group const groupCopy = group.map(cell => cell.slice()) - // 1. Filter the possible candidates + // 1. Filter the possible candidates (return if error) const possibleCandidates = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9] as const) - __filterPossibleCandidates(groupCopy, maxSize, possibleCandidates) + const __result = __filterPossibleCandidates(groupCopy, maxSize, possibleCandidates) + if (typeof __result === "string") { + return __result + } // c. Filter out cells that have too few candidates // (No limit on max candidates) - const possibleCells = [] as _CellInfoList + const possibleCells = [] as CellGroup for (let index: IndexToNine = 0; index < 9; index = index + 1 as IndexToNine) { const candidates = groupCopy[index] @@ -139,21 +143,36 @@ function findHiddenConjugatesOfGroup( maxSize = Math.min(maxSize, possibleCells.length, possibleCandidates.size) as 2 | 3 | 4 const conjugates = [] + const conjugateCands = [] // Only used in one location + for (const candidatesOfConjugate of combinations(Array.from(possibleCandidates), 2, maxSize)) { const conjugate = getConjugateFromCandidates(possibleCells, candidatesOfConjugate) + // if (candidatesOfConjugate.some(candidate => conjugate.every(cell => !cell.candidates.includes(candidate)))) { + // throw new TypeError(JSON.stringify([group, conjugate, candidatesOfConjugate])) + // } + // e.g.: 3 candidates must be in 2 cells - if (conjugate.length < candidatesOfConjugate.length) { + if (candidatesOfConjugate.length > conjugate.length) { return __errorHandling(candidatesOfConjugate, conjugate) - } else if (conjugate.length === candidatesOfConjugate.length) { - conjugates.push(conjugate) - - // Remove extra candidates - a conjugate was found! - for (const cell of conjugate) { - cell.candidates = cell.candidates.filter( + } else if (candidatesOfConjugate.length === conjugate.length) { + // Filter extra candidates - a conjugate was found! + const filteredConjugate = conjugate.map(cell => ({ + candidates: cell.candidates.filter( candidate => candidatesOfConjugate.includes(candidate) - ) + ), + position: cell.position, + })) + + // Check if this conjugate exactly overlaps a previous one + // If so, error just like above + for (const [i, prevConjugate] of conjugates.entries()) { + if (prevConjugate.length === conjugate.length && prevConjugate.every(cell => conjugate.some(cell2 => cell.position === cell2.position))) { + return __errorHandling([...new Set(candidatesOfConjugate.concat(conjugateCands[i]))], conjugate) + } } + conjugates.push(filteredConjugate) + conjugateCands.push(candidatesOfConjugate) } } @@ -162,7 +181,7 @@ function findHiddenConjugatesOfGroup( function findHiddenConjugatesOfSudoku(sudoku: PureSudoku, maxSize = 4 as 2 | 3 | 4) { - const conjugates = [] as _CellInfoList[] + const conjugates = [] as CellGroup[] for (const i of INDICES_TO_NINE) { const resultRow = findHiddenConjugatesOfGroup(sudoku.data[i], index => id(i, index), maxSize) if (typeof resultRow === "string") { diff --git a/src/Api/Strategies/pairsTriplesAndQuads.ts b/src/Api/Strategies/pairsTriplesAndQuads.ts index 972f4707..c5f75160 100644 --- a/src/Api/Strategies/pairsTriplesAndQuads.ts +++ b/src/Api/Strategies/pairsTriplesAndQuads.ts @@ -47,17 +47,16 @@ export function combinations(array: T[], min = 1, max = array.length, current } export type CellInfo = { - position: CellID candidates: SudokuDigits[] + position: CellID } -/** Really just a conjugate */ -export type _CellInfoList = CellInfo[] +export type CellGroup = CellInfo[] /** * Return a set of unique candidates in a conjugate */ -function getCandidatesOfConjugate(conjugate: _CellInfoList) { +function getCandidatesOfConjugate(conjugate: CellGroup) { // Array from the values of a set // The set is the accumulated candidates return conjugate.reduce( @@ -71,7 +70,7 @@ function getCandidatesOfConjugate(conjugate: _CellInfoList) { } // Inner inner function to make things look nicer below -function __errorHandling (conjugate: CellInfo[], invalidGroupCandidates: Set) { +function __errorHandling (conjugate: CellGroup, invalidGroupCandidates: Set) { const invalidGroupNames = convertArrayToEnglishList( conjugate.map(someCell => algebraic(someCell.position.row, someCell.position.column)) ) @@ -110,8 +109,8 @@ function findConjugatesOfGroup( maxSize = 4 as 2 | 3 | 4 ) { // 1. Filter the possible cells - // Each possible cell must have 2 to maxSize candidates - const possibleCells = [] as _CellInfoList + // Each possible cell must have from 2 to maxSize candidates + const possibleCells = [] as CellGroup for (const index of INDICES_TO_NINE) { const candidates = group[index] @@ -125,7 +124,7 @@ function findConjugatesOfGroup( } // 2. Now that the cells are filtered actually find the conjugates - const conjugates = [] as _CellInfoList[] + const conjugates = [] as CellGroup[] for (const conjugate of combinations(possibleCells, 2, maxSize)) { const candidatesOfConjugate = getCandidatesOfConjugate(conjugate) @@ -171,7 +170,7 @@ function findConjugatesOfSudoku(sudoku: PureSudoku, maxSize = 4 as 2 | 3 | 4) { /** * Colors a conjugate, see Cell#highlight */ -export function colorConjugate(sudoku: PureSudoku, conjugate: _CellInfoList, color = 'blue') { +export function colorConjugate(sudoku: PureSudoku, conjugate: CellGroup, color = 'blue') { if (sudoku instanceof Sudoku) { for (const cell of conjugate) { const element = sudoku.cells[cell.position.row][cell.position.column] @@ -182,7 +181,7 @@ export function colorConjugate(sudoku: PureSudoku, conjugate: _CellInfoList, col function eliminateUsingConjugateGroup( sudoku: PureSudoku, - conjugateGroup: _CellInfoList[][], + conjugateGroup: CellGroup[][], toGroupIndex: (id: CellID) => IndexToNine, toGroup: (sudoku: PureSudoku, index: IndexToNine) => SudokuDigits[][], toPosition: (indexInGroup: IndexToNine, indexOfGroup: IndexToNine) => CellID, @@ -223,8 +222,7 @@ function eliminateUsingConjugateGroup( return successcount } - -function eliminateUsingConjugateGroups(sudoku: PureSudoku, conjugateGroups: readonly [_CellInfoList[][], _CellInfoList[][], _CellInfoList[][]]) { +function eliminateUsingConjugateGroups(sudoku: PureSudoku, conjugateGroups: readonly [CellGroup[][], CellGroup[][], CellGroup[][]]) { const [resultRows, resultColumns, resultBoxes] = conjugateGroups let successcount = 0;