From 40250ebeaebcc4ba6eb94a2d48fc64398ab169c2 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Tue, 16 Jul 2024 17:02:44 +0200 Subject: [PATCH] Minor improvements to the solver cleanup code and improve performance --- .../kotlin/ch/dissem/yaep/domain/generator.kt | 39 ++++++------------- .../kotlin/ch/dissem/yaep/domain/GameTest.kt | 19 ++++----- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/domain/src/main/kotlin/ch/dissem/yaep/domain/generator.kt b/domain/src/main/kotlin/ch/dissem/yaep/domain/generator.kt index 00d35fe..6660440 100644 --- a/domain/src/main/kotlin/ch/dissem/yaep/domain/generator.kt +++ b/domain/src/main/kotlin/ch/dissem/yaep/domain/generator.kt @@ -1,8 +1,6 @@ package ch.dissem.yaep.domain -import ch.dissem.yaep.domain.PuzzleSolution.MULTIPLE_SOLUTIONS -import ch.dissem.yaep.domain.PuzzleSolution.NO_SOLUTION -import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE +import ch.dissem.yaep.domain.PuzzleSolution.* import kotlin.random.Random fun generateGame(size: Int = 6): Game { @@ -21,10 +19,18 @@ fun generateGame(size: Int = 6): Game { var i = 0 + // Run your solver on the reduced set of clues and count the number of possible solutions. + + // First do a binary search until the game isn't solvable anymore for the first time. + var halvedClues = clues.take(clues.size / 2) + while (solve(grid.toGrid(), halvedClues) == SOLVABLE) { + clues = halvedClues + halvedClues = clues.take(clues.size / 2) + } + // With the last solvable clues set, see what clues can be removed while the game is still solvable. // If i >= n, we are done. The set of clues is minimal. while (i < clues.size) { // Run your solver on the reduced set of clues and count the number of possible solutions. - // TODO: first do binary search val temp = clues - clues[i] if (solve(grid.toGrid(), temp) == SOLVABLE) { // If there is exactly one solution, update clues and reset index. @@ -33,27 +39,17 @@ fun generateGame(size: Int = 6): Game { } i++ } - return Game(grid.toGrid(), clues) - // (You can speed this up by removing clues in batches rather than one at a time, but it makes the algorithm more complicated to describe.) + return Game(grid.toGrid(), clues) } internal fun solve( grid: Grid, clues: Collection ): PuzzleSolution { - // Start with a grid where each cell is a list of all possible items. - - // First, set the positions of the items that are already known. -// clues.filterIsInstance>>().forEach { position -> -// position.removeForbiddenOptions(grid) -// } -// grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } } - // For each clue, remove any items that violate the clue. // If any cell has only one item left, remove that item from all other cells. // Repeat until no more items can be removed. -// val otherClues = clues.filter { it !is PositionClue<*> } var removedOptions: Boolean do { removedOptions = false @@ -66,9 +62,6 @@ internal fun solve( } // TODO clean up groups in rows (?) grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } } -// grid.forEach { row -> -// removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions -// } } while (removedOptions) // If any cell has no items left, the puzzle has no solution. @@ -77,16 +70,6 @@ internal fun solve( // If all cells have exactly one item left, the puzzle is solved. if (grid.cells.all { it.selection != null }) return SOLVABLE - // If there are still cells with multiple items, pick one and try each item in turn, then go back to step 2. -// return if ( -// grid.cells -// .filter { it.selection == null } -// .any { it.countSolutions(grid, clues) == 1 } -// ) { -// SOLVABLE -// } else { -// MULTIPLE_SOLUTIONS -// } return MULTIPLE_SOLUTIONS } diff --git a/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt b/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt index 6488806..fd2f23c 100644 --- a/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt +++ b/domain/src/test/kotlin/ch/dissem/yaep/domain/GameTest.kt @@ -1,14 +1,8 @@ package ch.dissem.yaep.domain -import ch.dissem.yaep.domain.Animal.ANT -import ch.dissem.yaep.domain.Animal.OCTOPUS -import ch.dissem.yaep.domain.Animal.SNAIL -import ch.dissem.yaep.domain.Nationality.CANADA -import ch.dissem.yaep.domain.Nationality.SWITZERLAND -import ch.dissem.yaep.domain.Nationality.UKRAINE -import ch.dissem.yaep.domain.Profession.ASTRONAUT -import ch.dissem.yaep.domain.Profession.FARMER -import ch.dissem.yaep.domain.Profession.SOFTWARE_DEV +import ch.dissem.yaep.domain.Animal.* +import ch.dissem.yaep.domain.Nationality.* +import ch.dissem.yaep.domain.Profession.* import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE import ch.tutteli.atrium.api.fluent.en_GB.feature import ch.tutteli.atrium.api.fluent.en_GB.toBeGreaterThan @@ -16,7 +10,7 @@ import ch.tutteli.atrium.api.fluent.en_GB.toBeLessThan import ch.tutteli.atrium.api.fluent.en_GB.toEqual import ch.tutteli.atrium.api.verbs.expect import kotlin.test.Test -import kotlin.time.Duration.Companion.seconds +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.measureTime class GameTest { @@ -36,8 +30,9 @@ class GameTest { } } println("Clues: ${game.clues.size}") + println("Time: $time") expect(solve(game.grid, game.clues)).toEqual(SOLVABLE) - expect(time).toBeLessThan(1.seconds) + expect(time).toBeLessThan(500.milliseconds) } @Test @@ -87,4 +82,4 @@ class GameTest { expect(solve(game.grid, game.clues)).toEqual(SOLVABLE) } -} \ No newline at end of file +}