diff --git a/src/main/kotlin/com/basdado/adventofcode/Day12.kt b/src/main/kotlin/com/basdado/adventofcode/Day12.kt new file mode 100644 index 0000000..0082c46 --- /dev/null +++ b/src/main/kotlin/com/basdado/adventofcode/Day12.kt @@ -0,0 +1,155 @@ +package com.basdado.adventofcode + +import java.util.* +import java.util.stream.Collectors +import kotlin.math.max +import kotlin.math.min + +const val INPUT = "/day/12/input.txt" + +fun main() { + val day = Day12() + day.puzzle1() + day.puzzle2() +} + +class Day12 { + + fun puzzle1() { + val initialState = parseInitialState() + val plantPatterns = parsePlantPatterns() + + var lastPlantState = getPlantStateAfterGeneration(initialState, plantPatterns, 20) + + println(lastPlantState.calcPlantSum()) + } + + fun puzzle2() { + + val initialState = parseInitialState() + val plantPatterns = parsePlantPatterns() + + val lastPlantState = getPlantStateAfterGeneration(initialState, plantPatterns, 50000000000L) + + println(lastPlantState.calcPlantSum()) + } + + private fun getPlantStateAfterGeneration( + initialState: BooleanArray, + plantPatterns: BooleanArray, + generationCount: Long + ): PlantState { + var lastPlantState = PlantState(0, initialState) +// println("0: $lastPlantState") + for (i in 1L..generationCount) { + val newPlantState = PlantState(lastPlantState.firstPlantIndex - 2, BooleanArray(lastPlantState.state.size + 4)) + for (plantIndex in (newPlantState.firstPlantIndex) until (newPlantState.firstPlantIndex + newPlantState.state.size)) { + val patternIndex = lastPlantState.getPatternIndex(plantIndex) + newPlantState.set(plantIndex, plantPatterns[patternIndex]) + } + + val trimmedNewState = newPlantState.trimmed() + if (lastPlantState.equalsShifted(trimmedNewState)) { + // So the only changes from now on is shifting the plants a little bit every generation + val shift = trimmedNewState.firstPlantIndex - lastPlantState.firstPlantIndex + val totalShift = shift * (generationCount - i) + return PlantState(trimmedNewState.firstPlantIndex + totalShift, trimmedNewState.state) + } + lastPlantState = trimmedNewState +// println("$i: $newPlantState") + } + return lastPlantState + } + + private fun parsePlantPatterns(): BooleanArray { + val plantPatternRegex = Regex("""([.#]{5}) => ([.#])""") + val plantPatternIndices = lines(INPUT) + .map { plantPatternRegex.matchEntire(it) } + .filter { it != null } + .filter { it!!.groupValues[2] == "#" } + .map { plantPatternToIndex(it!!.groupValues[1]) } + .collect(Collectors.toList()) + return BooleanArray(32) { plantPatternIndices.contains(it) } + } + + private fun parseInitialState(): BooleanArray { + val initialStateRegex = Regex("""initial state: ([.#]+)""") + return plantsToBooleanArray(lines(INPUT).filter { initialStateRegex.matches(it) }.findFirst() + .map { initialStateRegex.matchEntire(it) }.get().groupValues[1]) + } + + companion object { + + @JvmStatic + fun plantsToBooleanArray(plants: String): BooleanArray { + return plants.chars().mapToObj { it == '#'.toInt() }.toArray { Array(it) { false } }.toBooleanArray() + } + + @JvmStatic + fun plantPatternToIndex(plantPattern: String): Int { + assert(plantPattern.length == 5) + + var res = 0 + (0 until plantPattern.length).forEach { i -> + if (plantPattern[i] == '#') { + res = res or (1 shl i) + } + } + return res + } + } + + + + class PlantState(val firstPlantIndex: Long, val state: BooleanArray) { + val lastPlantIndex = firstPlantIndex + state.size + + fun getPatternIndex(plantIndex: Long): Int { + var res = 0 + for (i in 0 until 5) { + if (get(plantIndex + i - 2L)) { + res = res or (1 shl i) + } + } + return res + } + + private fun stateIndex(plantIndex: Long): Int = (plantIndex - firstPlantIndex).toInt() + private fun plantIndex(stateIndex: Int): Long = firstPlantIndex + stateIndex + + fun set(plantIndex: Long, value: Boolean) { + state[stateIndex(plantIndex)] = value + } + + fun get(plantIndex: Long): Boolean { + val stateIndex = stateIndex(plantIndex) + return stateIndex >= 0 && stateIndex < state.size && state[stateIndex] + } + + override fun toString(): String { + return state.joinToString("") { if (it) "#" else "." } + } + + fun calcPlantSum(): Long = (firstPlantIndex until (firstPlantIndex + state.size)).filter { get(it) }.sum() + + fun trimmed(): PlantState { + val firstPlantStateIndex = state.indexOfFirst { it } + val lastPlantStateIndex = state.indexOfLast { it } + if (firstPlantStateIndex == 0 && lastPlantStateIndex == state.size - 1) { + return this + } + return PlantState(plantIndex(firstPlantStateIndex), state.copyOfRange(firstPlantStateIndex, lastPlantStateIndex + 1)) + } + + override fun equals(other: Any?): Boolean { + return other is PlantState && + (min(firstPlantIndex, other.firstPlantIndex)..(max(lastPlantIndex, other.lastPlantIndex))).all { get(it) == other.get(it) } + } + + fun equalsShifted(other: PlantState): Boolean { + val thisTrimmed = this.trimmed() + val otherTrimmed = other.trimmed() + return Arrays.equals(thisTrimmed.state, otherTrimmed.state) + } + } +} \ No newline at end of file diff --git a/src/main/resources/day/12/example.txt b/src/main/resources/day/12/example.txt new file mode 100644 index 0000000..8335d88 --- /dev/null +++ b/src/main/resources/day/12/example.txt @@ -0,0 +1,16 @@ +initial state: #..#.#..##......###...### + +...## => # +..#.. => # +.#... => # +.#.#. => # +.#.## => # +.##.. => # +.#### => # +#.#.# => # +#.### => # +##.#. => # +##.## => # +###.. => # +###.# => # +####. => # \ No newline at end of file diff --git a/src/main/resources/day/12/input.txt b/src/main/resources/day/12/input.txt new file mode 100644 index 0000000..c9e4a95 --- /dev/null +++ b/src/main/resources/day/12/input.txt @@ -0,0 +1,34 @@ +initial state: ..#..####.##.####...#....#######..#.#..#..#.#.#####.######..#.#.#.#..##.###.#....####.#.#....#.##### + +#.##. => . +#.#.. => . +###.# => . +..#.# => . +....# => . +.#### => . +##.## => # +###.. => # +.###. => # +...#. => . +..... => . +##..# => . +.#.#. => # +.#.## => # +##.#. => . +##... => . +##### => # +#...# => . +..##. => . +..### => . +.#... => # +.##.# => . +#.... => . +.#..# => . +.##.. => # +...## => # +#.### => . +#..#. => . +..#.. => # +#.#.# => # +####. => # +#..## => . diff --git a/src/main/resources/day/12/test.txt b/src/main/resources/day/12/test.txt new file mode 100644 index 0000000..02cf6df --- /dev/null +++ b/src/main/resources/day/12/test.txt @@ -0,0 +1,3 @@ +initial state: #..# +...#. => # +.#... => # \ No newline at end of file