Minor improvements to the solver

cleanup code and improve performance
This commit is contained in:
Christian Basler
2024-07-16 17:02:44 +02:00
parent 7190e4faef
commit 40250ebeae
2 changed files with 18 additions and 40 deletions

View File

@@ -1,8 +1,6 @@
package ch.dissem.yaep.domain package ch.dissem.yaep.domain
import ch.dissem.yaep.domain.PuzzleSolution.MULTIPLE_SOLUTIONS import ch.dissem.yaep.domain.PuzzleSolution.*
import ch.dissem.yaep.domain.PuzzleSolution.NO_SOLUTION
import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE
import kotlin.random.Random import kotlin.random.Random
fun generateGame(size: Int = 6): Game { fun generateGame(size: Int = 6): Game {
@@ -21,10 +19,18 @@ fun generateGame(size: Int = 6): Game {
var i = 0 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. // If i >= n, we are done. The set of clues is minimal.
while (i < clues.size) { while (i < clues.size) {
// Run your solver on the reduced set of clues and count the number of possible solutions. // 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] val temp = clues - clues[i]
if (solve(grid.toGrid(), temp) == SOLVABLE) { if (solve(grid.toGrid(), temp) == SOLVABLE) {
// If there is exactly one solution, update clues and reset index. // If there is exactly one solution, update clues and reset index.
@@ -33,27 +39,17 @@ fun generateGame(size: Int = 6): Game {
} }
i++ 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( internal fun solve(
grid: Grid, grid: Grid,
clues: Collection<Clue> clues: Collection<Clue>
): PuzzleSolution { ): 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<PositionClue<ItemClass<*>>>().forEach { position ->
// position.removeForbiddenOptions(grid)
// }
// grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
// For each clue, remove any items that violate the clue. // 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. // If any cell has only one item left, remove that item from all other cells.
// Repeat until no more items can be removed. // Repeat until no more items can be removed.
// val otherClues = clues.filter { it !is PositionClue<*> }
var removedOptions: Boolean var removedOptions: Boolean
do { do {
removedOptions = false removedOptions = false
@@ -66,9 +62,6 @@ internal fun solve(
} }
// TODO clean up groups in rows (?) // TODO clean up groups in rows (?)
grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } } grid.forEach { row -> row.forEach { cell -> row.cleanupOptions(cell) } }
// grid.forEach { row ->
// removedOptions = row.tryOptionsForClues(grid, otherClues) || removedOptions
// }
} while (removedOptions) } while (removedOptions)
// If any cell has no items left, the puzzle has no solution. // 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 all cells have exactly one item left, the puzzle is solved.
if (grid.cells.all { it.selection != null }) return SOLVABLE 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 return MULTIPLE_SOLUTIONS
} }

View File

@@ -1,14 +1,8 @@
package ch.dissem.yaep.domain package ch.dissem.yaep.domain
import ch.dissem.yaep.domain.Animal.ANT import ch.dissem.yaep.domain.Animal.*
import ch.dissem.yaep.domain.Animal.OCTOPUS import ch.dissem.yaep.domain.Nationality.*
import ch.dissem.yaep.domain.Animal.SNAIL import ch.dissem.yaep.domain.Profession.*
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.PuzzleSolution.SOLVABLE import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE
import ch.tutteli.atrium.api.fluent.en_GB.feature import ch.tutteli.atrium.api.fluent.en_GB.feature
import ch.tutteli.atrium.api.fluent.en_GB.toBeGreaterThan 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.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.measureTime import kotlin.time.measureTime
class GameTest { class GameTest {
@@ -36,8 +30,9 @@ class GameTest {
} }
} }
println("Clues: ${game.clues.size}") println("Clues: ${game.clues.size}")
println("Time: $time")
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE) expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
expect(time).toBeLessThan(1.seconds) expect(time).toBeLessThan(500.milliseconds)
} }
@Test @Test
@@ -87,4 +82,4 @@ class GameTest {
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE) expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
} }
} }