From 824296e9b8e43a3c69ba77151e28611d0254c97a Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Wed, 11 Dec 2024 03:36:51 -0500 Subject: [PATCH 1/2] Day 11 complete. --- src/main/kotlin/day10/day10.kt | 2 +- src/main/kotlin/day11/day11.kt | 68 ++++++++++++++++++++++++++++++++++ src/test/kotlin/day11/day11.kt | 21 +++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/day11/day11.kt create mode 100644 src/test/kotlin/day11/day11.kt diff --git a/src/main/kotlin/day10/day10.kt b/src/main/kotlin/day10/day10.kt index ed822a8..124f029 100644 --- a/src/main/kotlin/day10/day10.kt +++ b/src/main/kotlin/day10/day10.kt @@ -1,4 +1,4 @@ -// Advent of Code 2024, Day 09. +// Advent of Code 2024, Day 10. // By Sebastian Raaphorst, 2024. package day10 diff --git a/src/main/kotlin/day11/day11.kt b/src/main/kotlin/day11/day11.kt new file mode 100644 index 0000000..822f29c --- /dev/null +++ b/src/main/kotlin/day11/day11.kt @@ -0,0 +1,68 @@ +// Advent of Code 2024, Day 11. +// By Sebastian Raaphorst, 2024. + +package day11 + +import common.aocreader.fetchAdventOfCodeInput +import common.parsing.WhitespaceParser + +private fun parse(input: String): MutableMap { + val result = mutableMapOf() + input.split(WhitespaceParser).map(String::toLong).forEach { num -> + result[num] = result.getOrDefault(num, 0) + 1 + } + return result +} + +// The key is that the order of the stones have no relevance. +private object StoneRules { + private val memoization: MutableMap> = mutableMapOf() + + private fun rule(stone: Long): List = + memoization.computeIfAbsent(stone) { num -> + if (num == 0L) return@computeIfAbsent listOf(1L) + + val asString = num.toString() + if (asString.length % 2 == 0) { + val middle = asString.length / 2 + val firstHalf = asString.substring(0, middle).toLong() + val secondHalf = asString.substring(middle).toLong() + return@computeIfAbsent listOf(firstHalf, secondHalf) + } + + return@computeIfAbsent listOf(stone * 2024) + } + + fun blink(stones: MutableMap): MutableMap { + val newStones = mutableMapOf() + for ((stone, count) in stones) { + val transformed = rule(stone) + for (tStone in transformed) { + newStones[tStone] = newStones.getOrDefault(tStone, 0L) + count + } + } + return newStones + } +} + +private fun answer(input: String, blinks: Int): Long = + (1..blinks).fold(parse(input)) { stones, _ -> StoneRules.blink(stones) } + .values.sum() + +fun answer1(input: String): Long = + answer(input, 25) + +fun answer2(input: String): Long = + answer(input, 75) + +fun main() { + val input = fetchAdventOfCodeInput(2024, 11) + + println("--- Day 11: Plutonian Pebbles ---") + + // Part 1:185894, memoization cache has size 1381. + println("Part 1: ${answer1(input)}") + + // Part 2: 221632504974231, memoization cache has size 3896. + println("Part 2: ${answer2(input)}") +} diff --git a/src/test/kotlin/day11/day11.kt b/src/test/kotlin/day11/day11.kt new file mode 100644 index 0000000..daca4d4 --- /dev/null +++ b/src/test/kotlin/day11/day11.kt @@ -0,0 +1,21 @@ +// Advent of Code 2024, Day 11. +// By Sebastian Raaphorst, 2024. + +package day11 + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +// Note: no problem 2 test today. +class Day11Test { + private companion object { + val input = + """ + 125 17 + """.trimIndent().trim() + } + + @Test + fun `Problem 1 example`() = + assertEquals(55312L, answer1(input)) +} \ No newline at end of file From 0c03871393e6dba47bda9e88ae898b71ec9cc449 Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Wed, 11 Dec 2024 04:30:02 -0500 Subject: [PATCH 2/2] Day 11: Timing added and general cleaning done.. --- src/main/kotlin/common/aocreader/aocreader.kt | 3 + .../common/collectionops/collectionops.kt | 2 +- src/main/kotlin/common/input.kt | 18 ----- src/main/kotlin/common/intpos2d/intpos2d.kt | 3 + src/main/kotlin/common/parsing/parsing.kt | 2 +- src/main/kotlin/common/runner/timed.kt | 12 +++ src/main/kotlin/common/stringops.kt | 2 +- src/main/kotlin/day01/day01.kt | 10 +-- src/main/kotlin/day02/day02.kt | 10 +-- src/main/kotlin/day03/day03.kt | 10 +-- src/main/kotlin/day04/day04.kt | 10 +-- src/main/kotlin/day05/day05.kt | 10 +-- src/main/kotlin/day06/day06.kt | 13 +--- src/main/kotlin/day07/day07.kt | 12 +-- src/main/kotlin/day08/day08.kt | 10 +-- src/main/kotlin/day09/day09.kt | 10 +-- src/main/kotlin/day10/day10.kt | 10 +-- src/main/kotlin/day11/day11.kt | 74 +++++++++---------- src/test/kotlin/day04/day04.kt | 1 - 19 files changed, 87 insertions(+), 135 deletions(-) delete mode 100644 src/main/kotlin/common/input.kt create mode 100644 src/main/kotlin/common/runner/timed.kt diff --git a/src/main/kotlin/common/aocreader/aocreader.kt b/src/main/kotlin/common/aocreader/aocreader.kt index a5b6549..1ea0706 100644 --- a/src/main/kotlin/common/aocreader/aocreader.kt +++ b/src/main/kotlin/common/aocreader/aocreader.kt @@ -1,3 +1,6 @@ +// Advent of Code 2024 +// By Sebastian Raaphorst, 2024. + package common.aocreader import java.net.HttpURLConnection diff --git a/src/main/kotlin/common/collectionops/collectionops.kt b/src/main/kotlin/common/collectionops/collectionops.kt index 759f681..0acc3e1 100644 --- a/src/main/kotlin/common/collectionops/collectionops.kt +++ b/src/main/kotlin/common/collectionops/collectionops.kt @@ -1,4 +1,4 @@ -// Advent of Code +// Advent of Code 2024 // By Sebastian Raaphorst, 2024. package common.collectionops diff --git a/src/main/kotlin/common/input.kt b/src/main/kotlin/common/input.kt deleted file mode 100644 index cdef72e..0000000 --- a/src/main/kotlin/common/input.kt +++ /dev/null @@ -1,18 +0,0 @@ -// Advent of Code -// By Sebastian Raaphorst, 2024. - -package common - -import kotlin.reflect.KClass - -/** - * Extract the name of the day. - */ -fun KClass<*>.day(): String = - this.java.packageName ?: throw RuntimeException("No package name found") - -/** - * Read the input for the day. - */ -fun readInput(day: String): String = - {}::class.java.getResource("/$day.txt")!!.readText() diff --git a/src/main/kotlin/common/intpos2d/intpos2d.kt b/src/main/kotlin/common/intpos2d/intpos2d.kt index 0b71bcc..6cd6dc8 100644 --- a/src/main/kotlin/common/intpos2d/intpos2d.kt +++ b/src/main/kotlin/common/intpos2d/intpos2d.kt @@ -1,3 +1,6 @@ +// Advent of Code 2024 +// By Sebastian Raaphorst, 2024. + package common.intpos2d typealias IntPos2D = Pair diff --git a/src/main/kotlin/common/parsing/parsing.kt b/src/main/kotlin/common/parsing/parsing.kt index 3318273..7a71a28 100644 --- a/src/main/kotlin/common/parsing/parsing.kt +++ b/src/main/kotlin/common/parsing/parsing.kt @@ -1,4 +1,4 @@ -// Advent of Code +// Advent of Code 2024 // By Sebastian Raaphorst, 2024. package common.parsing diff --git a/src/main/kotlin/common/runner/timed.kt b/src/main/kotlin/common/runner/timed.kt new file mode 100644 index 0000000..e61a798 --- /dev/null +++ b/src/main/kotlin/common/runner/timed.kt @@ -0,0 +1,12 @@ +// Advent of Code 2024 +// By Sebastian Raaphorst, 2024. + +package common.runner + +inline fun timedFunction(functionName: String, block: () -> T): T { + val startTime = System.nanoTime() + val result = block() + val endTime = System.nanoTime() + println("$functionName: $result (${(endTime - startTime) / 1_000_000.0} ms)") + return result +} diff --git a/src/main/kotlin/common/stringops.kt b/src/main/kotlin/common/stringops.kt index 381fbf5..a847db2 100644 --- a/src/main/kotlin/common/stringops.kt +++ b/src/main/kotlin/common/stringops.kt @@ -1,4 +1,4 @@ -// Advent of Code +// Advent of Code 2024 // By Sebastian Raaphorst, 2024. package common diff --git a/src/main/kotlin/day01/day01.kt b/src/main/kotlin/day01/day01.kt index 93f005c..6f7ac05 100644 --- a/src/main/kotlin/day01/day01.kt +++ b/src/main/kotlin/day01/day01.kt @@ -6,6 +6,7 @@ package day01 import common.aocreader.fetchAdventOfCodeInput import common.parsing.parseColumns import common.collectionops.toFrequencyMap +import common.runner.timedFunction import kotlin.math.abs fun answer1(input: String): Int = @@ -25,12 +26,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 1) - println("--- Day 1: Historian Hysteria ---") - - // Answer 1: 2031679 - println("Part 1: ${answer1(input)}") - - // Answer 2: 19678534 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 2031679 + timedFunction("Part 2") { answer2(input) } // 19678534 } \ No newline at end of file diff --git a/src/main/kotlin/day02/day02.kt b/src/main/kotlin/day02/day02.kt index 91a1e1e..db2b1b5 100644 --- a/src/main/kotlin/day02/day02.kt +++ b/src/main/kotlin/day02/day02.kt @@ -6,6 +6,7 @@ package day02 import common.aocreader.fetchAdventOfCodeInput import common.collectionops.allListDrops import common.parsing.parseGrid +import common.runner.timedFunction private const val Lower = 1 private const val Upper = 3 @@ -30,12 +31,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 2) - println("--- Day 2: Red-Nosed Reports ---") - - // Answer 1: 379 - println("Part 1: ${answer1(input)}") - - // Answer 2: 430 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 379 + timedFunction("Part 2") { answer2(input) } // 430 } \ No newline at end of file diff --git a/src/main/kotlin/day03/day03.kt b/src/main/kotlin/day03/day03.kt index 1fc1beb..cc1e137 100644 --- a/src/main/kotlin/day03/day03.kt +++ b/src/main/kotlin/day03/day03.kt @@ -4,6 +4,7 @@ package day03 import common.aocreader.fetchAdventOfCodeInput +import common.runner.timedFunction /** * We want to turn off processing for substrings of the form: @@ -31,12 +32,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 3) - println("--- Day 3: Mull It Over ---") - - // Answer 1: 173785482 - println("Part 1: ${answer1(input)}") - - // Answer 2: 83158140 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 173785482 + timedFunction("Part 2") { answer2(input) } // 83158140 } \ No newline at end of file diff --git a/src/main/kotlin/day04/day04.kt b/src/main/kotlin/day04/day04.kt index ee942ca..41e3363 100644 --- a/src/main/kotlin/day04/day04.kt +++ b/src/main/kotlin/day04/day04.kt @@ -6,6 +6,7 @@ package day04 import common.aocreader.fetchAdventOfCodeInput import common.countSubstrings import common.collectionops.* +import common.runner.timedFunction private const val XMAS = "XMAS" @@ -60,12 +61,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 4) - println("--- Day 4: Ceres Search ---") - - // Answer 1: 2370 - println("Part 1: ${answer1(input)}") - - // Answer 2: 1908 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 2370 + timedFunction("Part 2") { answer2(input) } // 1908 } \ No newline at end of file diff --git a/src/main/kotlin/day05/day05.kt b/src/main/kotlin/day05/day05.kt index 532a28f..bd8765f 100644 --- a/src/main/kotlin/day05/day05.kt +++ b/src/main/kotlin/day05/day05.kt @@ -5,6 +5,7 @@ package day05 import common.aocreader.fetchAdventOfCodeInput import common.collectionops.middle +import common.runner.timedFunction private typealias OrderingRules = Map> private typealias Updates = List @@ -96,12 +97,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 5) - println("--- Day 5: Print Queue ---") - - // Part 1: 4281 - println("Part 1: ${answer1(input)}") - - // Part 2: 5466 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 4281 + timedFunction("Part 2") { answer2(input) } // 5466 } diff --git a/src/main/kotlin/day06/day06.kt b/src/main/kotlin/day06/day06.kt index e29193f..83b941a 100644 --- a/src/main/kotlin/day06/day06.kt +++ b/src/main/kotlin/day06/day06.kt @@ -1,13 +1,11 @@ // Advent of Code 2024, Day 06. // By Sebastian Raaphorst, 2024. -// NOTE: Trying to use pure FP in this question made part 2 run extremely slowly. -// Mutable data structures are needed to avoid having to continuously copy structures. - package day06 import common.aocreader.fetchAdventOfCodeInput import common.intpos2d.* +import common.runner.timedFunction private typealias Orientation = Pair @@ -84,12 +82,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 6) - println("--- Day 6: Guard Gallivant ---") - - // Part 1: 5208 - println("Part 1: ${answer1(input)}") - - // Part 2: 1972 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 5208 + timedFunction("Part 2") { answer2(input) } // 1972 } diff --git a/src/main/kotlin/day07/day07.kt b/src/main/kotlin/day07/day07.kt index b225822..2eb8244 100644 --- a/src/main/kotlin/day07/day07.kt +++ b/src/main/kotlin/day07/day07.kt @@ -4,9 +4,8 @@ package day07 import common.aocreader.fetchAdventOfCodeInput -import common.day import common.parsing.WhitespaceParser -import common.readInput +import common.runner.timedFunction import java.math.BigInteger data class Equation(val total: BigInteger, val numbers: List) { @@ -61,12 +60,7 @@ fun answer2(input: String): BigInteger = fun main() { val input = fetchAdventOfCodeInput(2024, 7) - println("--- Day 7: Bridge Repair ---") - - // Part 1: 2437272016585 - println("Part 1: ${answer1(input)}") - - // Part 2: 162987117690649 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 2437272016585 + timedFunction("Part 2") { answer2(input) } // 162987117690649 } diff --git a/src/main/kotlin/day08/day08.kt b/src/main/kotlin/day08/day08.kt index 6d76d1b..139c9c3 100644 --- a/src/main/kotlin/day08/day08.kt +++ b/src/main/kotlin/day08/day08.kt @@ -5,6 +5,7 @@ package day08 import common.aocreader.fetchAdventOfCodeInput import common.intpos2d.* +import common.runner.timedFunction private typealias Frequency = Char private typealias AntennaMap = Map> @@ -83,12 +84,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 8) - println("--- Day 8: Resonant Collinearity ---") - - // Part 1: 376 - println("Part 1: ${answer1(input)}") - - // Part 2: 1352 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 376 + timedFunction("Part 2") { answer2(input) } // 1352 } diff --git a/src/main/kotlin/day09/day09.kt b/src/main/kotlin/day09/day09.kt index 11494b6..e485db4 100644 --- a/src/main/kotlin/day09/day09.kt +++ b/src/main/kotlin/day09/day09.kt @@ -4,6 +4,7 @@ package day09 import common.aocreader.fetchAdventOfCodeInput +import common.runner.timedFunction import java.math.BigInteger private typealias Range = LongRange @@ -190,12 +191,7 @@ fun answer2(input: String): BigInteger = fun main() { val input = fetchAdventOfCodeInput(2024, 9) - println("--- Day 9: Disk Fragmenter ---") - - // Part 1: 6384282079460 - println("Part 1: ${answer1(input)}") - - // Part 2: 6408966547049 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 6384282079460 + timedFunction("Part 2") { answer2(input) } // 6408966547049 } diff --git a/src/main/kotlin/day10/day10.kt b/src/main/kotlin/day10/day10.kt index 124f029..66b13d9 100644 --- a/src/main/kotlin/day10/day10.kt +++ b/src/main/kotlin/day10/day10.kt @@ -5,6 +5,7 @@ package day10 import common.aocreader.fetchAdventOfCodeInput import common.intpos2d.* +import common.runner.timedFunction private typealias Trail = List private typealias Trails = Set> @@ -57,12 +58,7 @@ fun answer2(input: String): Int = fun main() { val input = fetchAdventOfCodeInput(2024, 10) - println("--- Day 10: Hoof It ---") - - // Part 1: 719 - println("Part 1: ${answer1(input)}") - - // Part 2: 1530 - println("Part 2: ${answer2(input)}") + timedFunction("Part 1") { answer1(input) } // 719 + timedFunction("Part 2") { answer2(input) } // 1530 } diff --git a/src/main/kotlin/day11/day11.kt b/src/main/kotlin/day11/day11.kt index 822f29c..f4c9662 100644 --- a/src/main/kotlin/day11/day11.kt +++ b/src/main/kotlin/day11/day11.kt @@ -5,44 +5,45 @@ package day11 import common.aocreader.fetchAdventOfCodeInput import common.parsing.WhitespaceParser +import common.runner.timedFunction +import day11.StoneRules.cacheSize +import java.util.concurrent.ConcurrentHashMap -private fun parse(input: String): MutableMap { - val result = mutableMapOf() - input.split(WhitespaceParser).map(String::toLong).forEach { num -> - result[num] = result.getOrDefault(num, 0) + 1 - } - return result -} + +private fun parse(input: String): MutableMap = + input.split(WhitespaceParser) + .map(String::toLong) + .groupingBy { it } + .eachCount() + .mapValues { it.value.toLong() } + .toMutableMap() // The key is that the order of the stones have no relevance. private object StoneRules { - private val memoization: MutableMap> = mutableMapOf() - - private fun rule(stone: Long): List = - memoization.computeIfAbsent(stone) { num -> - if (num == 0L) return@computeIfAbsent listOf(1L) - - val asString = num.toString() - if (asString.length % 2 == 0) { - val middle = asString.length / 2 - val firstHalf = asString.substring(0, middle).toLong() - val secondHalf = asString.substring(middle).toLong() - return@computeIfAbsent listOf(firstHalf, secondHalf) - } - - return@computeIfAbsent listOf(stone * 2024) - } + private val memoization: MutableMap> = ConcurrentHashMap() - fun blink(stones: MutableMap): MutableMap { - val newStones = mutableMapOf() - for ((stone, count) in stones) { - val transformed = rule(stone) - for (tStone in transformed) { - newStones[tStone] = newStones.getOrDefault(tStone, 0L) + count + private fun rule(stone: Long): List = memoization.computeIfAbsent(stone) { + when { + it == 0L -> listOf(1L) + it.toString().length % 2 == 0 -> { + val middle = it.toString().length / 2 + listOf( + it.toString().substring(0, middle).toLong(), + it.toString().substring(middle).toLong() + ) } + else -> listOf(it * 2024) } - return newStones } + + fun blink(stones: Map): MutableMap = + stones.flatMap { (stone, count) -> + rule(stone).map { transformed -> transformed to count } + }.groupingBy { it.first } + .fold(0L) { acc, (_, count) -> acc + count } + .toMutableMap() + + fun cacheSize(): Int = memoization.size } private fun answer(input: String, blinks: Int): Long = @@ -57,12 +58,9 @@ fun answer2(input: String): Long = fun main() { val input = fetchAdventOfCodeInput(2024, 11) - println("--- Day 11: Plutonian Pebbles ---") - - // Part 1:185894, memoization cache has size 1381. - println("Part 1: ${answer1(input)}") - - // Part 2: 221632504974231, memoization cache has size 3896. - println("Part 2: ${answer2(input)}") -} + timedFunction("Part 1") { answer1(input) } + println("\tCache size: ${cacheSize()}") + timedFunction("Part 2") { answer2(input) } + println("\tCache size: ${cacheSize()}") +} \ No newline at end of file diff --git a/src/test/kotlin/day04/day04.kt b/src/test/kotlin/day04/day04.kt index 0c7a0c5..3048ed2 100644 --- a/src/test/kotlin/day04/day04.kt +++ b/src/test/kotlin/day04/day04.kt @@ -21,7 +21,6 @@ class Day04Test { MAMMMXMMMM MXMXAXMASX """.trimIndent() - } @Test