diff --git a/src/main/kotlin/no/rodland/advent_2023/Day12.kt b/src/main/kotlin/no/rodland/advent_2023/Day12.kt index b1e52597..a6b5645e 100644 --- a/src/main/kotlin/no/rodland/advent_2023/Day12.kt +++ b/src/main/kotlin/no/rodland/advent_2023/Day12.kt @@ -5,25 +5,82 @@ import no.rodland.advent.Day // template generated: 12/12/2023 // Fredrik Rødland 2023 -class Day12(val input: List) : Day>>> { +class Day12(val input: List) : Day> { private val parsed = input.parse() override fun partOne(): Int { - return parsed.sumOf { (candidates, numbers) -> - expand(candidates) - .map { candidate -> candidate.split("\\.+".toRegex()).filterNot { it.isEmpty() } } - .filter { it.size == numbers.size } - .map { strings -> strings.map { it.length } } - .count { it == numbers } + return parsed.sumOf { (pattern, numbers) -> + count(pattern, numbers).toInt() } } override fun partTwo(): Long { - return 2 + return parsed.map { it.expand() }.sumOf { (pattern, numbers) -> + count(pattern, numbers) + } + } + + private val cache = mutableMapOf>, Long>() + private fun count(pattern: String, groups: List): Long { + return cache.getOrPut(pattern to groups) { + if (pattern.isEmpty()) { + return if (groups.isEmpty()) 1L else 0L + } + when (pattern[0]) { + '.' -> count(pattern.drop(1), groups) + '#' -> countHash(pattern, groups) + '?' -> count(pattern.drop(1), groups) + count(pattern.replaceFirst('?', '#'), groups) + else -> error("Should not happen") + } + } + } + + private fun countHash(pattern: String, groups: List): Long { + if (groups.isEmpty()) return 0L + val number = groups.first() + return when { + groups.isEmpty() -> 0L + pattern.length < number -> 0L + '.' in pattern.substring(0.. 0L + pattern.length == number && groups.size == 1 -> 1L + pattern.length == number && groups.size != 1 -> 0L + pattern[number] == '#' -> 0L + else -> count(pattern.drop(number + 1), groups.drop(1)) + } } + // https://www.reddit.com/r/adventofcode/comments/18ge41g/comment/kd0wo99/ + // https://github.com/eagely/adventofcode/blob/main/src/main/kotlin/solutions/y2023/Day12.kt + private val mem = hashMapOf>, Long>() + + @Suppress("unused") + private fun eagely(config: String, groups: List): Long { + if (groups.isEmpty()) return if ("#" in config) 0 else 1 + if (config.isEmpty()) return 0 + + return mem.getOrPut(config to groups) { + var result = 0L + if (config.first() in ".?") + result += eagely(config.drop(1), groups) + if (config.first() in "#?" && groups.first() <= config.length && "." !in config.take(groups.first()) && (groups.first() == config.length || config[groups.first()] != '#')) + result += eagely(config.drop(groups.first() + 1), groups.drop(1)) + result + } + } + + @Suppress("unused") + fun bruteForce(): Int { + return parsed.sumOf { (patterns, numbers) -> + expand(patterns) + .map { pattern -> pattern.split("\\.+".toRegex()).filterNot { it.isEmpty() } } + .filter { it.size == numbers.size } + .map { strings -> strings.map { it.length } } + .count { it == numbers } + } + } + fun expand(input: String): List { val count = input.count { it == '?' } val numChars = 1 shl count // 2^count @@ -33,14 +90,18 @@ class Day12(val input: List) : Day boolean.toMapString() } + .map { width -> width.toMapString() } .map { mask -> mask.fold(input) { acc: String, c: Char -> acc.replaceFirst("?", c.toString()) } } } - override fun List.parse(): List>> { + data class Springs(val pattern: String, val numbers: List) { + fun expand() = Springs(List(5) { pattern }.joinToString("?"), List(5) { numbers }.flatten()) + } + + override fun List.parse(): List { return map { line -> val (first, second) = line.split(" ") - first to second.split(",").map { it.toInt() } + Springs(first, second.split(",").map { it.toInt() }) } } diff --git a/src/test/kotlin/no/rodland/advent_2023/Day12Test.kt b/src/test/kotlin/no/rodland/advent_2023/Day12Test.kt index ad4c7911..13328bed 100644 --- a/src/test/kotlin/no/rodland/advent_2023/Day12Test.kt +++ b/src/test/kotlin/no/rodland/advent_2023/Day12Test.kt @@ -16,9 +16,9 @@ internal class Day12Test { private val test12 = "2023/input_12_test.txt".readFile() private val resultTestOne = 21 - private val resultTestTwo = 2L + private val resultTestTwo = 525152L private val resultOne = 7163 - private val resultTwo = 2L + private val resultTwo = 17788038834112L val test = defaultTestSuiteParseOnInit( Day12(data12), @@ -29,8 +29,8 @@ internal class Day12Test { resultTwo, { Day12(data12) }, { Day12(test12) }, - numTestPart1 = 1, - numTestPart2 = 1, + numTestPart1 = 50, + numTestPart2 = 50, ) @Nested @@ -108,7 +108,7 @@ internal class Day12Test { } @Test - @Slow(10000) +// @Slow(10000) fun `12,1,live,1`() { report(test.livePart1) }