diff --git a/build.gradle.kts b/build.gradle.kts index 10400b4..a722199 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { } dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") } tasks.test { diff --git a/src/main/kotlin/day04/Day04.kt b/src/main/kotlin/day04/Day04.kt index 0d144a3..7a1c91e 100644 --- a/src/main/kotlin/day04/Day04.kt +++ b/src/main/kotlin/day04/Day04.kt @@ -3,17 +3,11 @@ package day04 import fetchInput import println import kotlin.math.pow -import kotlin.system.measureTimeMillis fun main() { val input = fetchInput(4) part1(input).println() - val executionTime = measureTimeMillis { - val result = part2(input) - result.println() - } - println("Execution time: $executionTime ms") -// part2(input).println() + part2(input).println() } fun part1(input: List): Int { @@ -36,7 +30,6 @@ fun part2(input: List): Int { } } } - println(cardTracker.asList().toString()) return cardTracker.sum() } @@ -45,23 +38,24 @@ fun Triple, Set>.numOfMatches(): Int = (second intersect thir fun Int.numOfPoints(): Int = 2f.pow(this - 1).toInt() fun String.parseInput(): Triple, Set> { - val parts = this.split(":", "|") + val parts = split(":", " | ") require(parts.size == 3) { "String failed to parse: $this" } + val cardNum = parts[0] .substringAfter("Card ") .trim() .toInt() + val winningNums = parts[1] - .trim() .split(" ") .filter { it.isNotBlank() } .map(String::toInt) .toSet() + val myNums = parts[2] - .trim() .split(" ") .filter { it.isNotBlank() } .map(String::toInt) diff --git a/src/main/kotlin/day05/Day05.kt b/src/main/kotlin/day05/Day05.kt new file mode 100644 index 0000000..06f7b2e --- /dev/null +++ b/src/main/kotlin/day05/Day05.kt @@ -0,0 +1,117 @@ +package day05 + +import fetchInput +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking +import println + +fun main() { + val input = fetchInput(5) + part1(input).println() + part2(input).println() +} + +fun part1(input: List): Long { + val parsed = parseDay5(input) + return parsed.seeds.minOf { + seedToLocation(it, parsed, input) + } +} + +fun part2(input: List): Long = + runBlocking { + val parsed = parseDay5(input) + val newSeeds = + parsed.seeds + .chunked(2) + .map { (seed, range) -> + seed until seed + range + } + + val locations = mutableSetOf() + + newSeeds.map { longRange -> + async { + var min: Long = Long.MAX_VALUE + for (seed in longRange) { + min = minOf(seedToLocation(seed, parsed, input), min) + } + min + } + }.awaitAll().forEach { + locations.add(it) + } + locations.min() + } + +data class Day05Game( + val seeds: List, + val sToSMap: String, + val sToFMap: String, + val fToWMap: String, + val wToLMap: String, + val lToTMap: String, + val tToHMap: String, + val hToLMap: String, +) + +fun parseDay5(input: List): Day05Game { + // a regex is probably way simpler. + val seeds = + input + .first() + .split(": ") + .drop(1) + .first() + .split(" ") + .map(String::toLong) + + return Day05Game( + seeds = seeds, + sToSMap = "seed-to-soil map:", + sToFMap = "soil-to-fertilizer map:", + fToWMap = "fertilizer-to-water map:", + wToLMap = "water-to-light map:", + lToTMap = "light-to-temperature map:", + tToHMap = "temperature-to-humidity map:", + hToLMap = "humidity-to-location map:", + ) +} + +private fun convertToDestination( + seed: Long, + mapName: String, + input: List, +): Long { + val startIndex = input.indexOf(mapName) + 1 + + for (i in startIndex until input.size) { + val line = input[i] + if (line.isBlank()) break + + val parsed = line.split(" ").map(String::toLong) + + val (destination, source, range) = parsed + if (seed in source until source + range) { + val offset = destination - source + return seed + offset + } + } + return seed +} + +fun seedToLocation( + seed: Long, + game: Day05Game, + input: List, +): Long { + var key = seed + val transformations = + listOf(game.sToSMap, game.sToFMap, game.fToWMap, game.wToLMap, game.lToTMap, game.tToHMap, game.hToLMap) + for (transform in transformations) { + val value = convertToDestination(key, transform, input) + key = value + } + return key +} diff --git a/src/test/kotlin/day05/Day05Test.kt b/src/test/kotlin/day05/Day05Test.kt new file mode 100644 index 0000000..16218b7 --- /dev/null +++ b/src/test/kotlin/day05/Day05Test.kt @@ -0,0 +1,73 @@ +package day05 + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class Day05Test { + @Test + fun seedReturnsCorrectSoil() { + val expected = listOf(82L, 43L, 86L, 35L) + val parsed = parseDay5(sampleInput) + val seeds = parsed.seeds + val actual = seeds.map { seedToLocation(it, parsed, sampleInput) } + assertEquals(expected, actual) + } + + @Test + fun part2ReturnsLowestLocation() { + val expected = 46L + val parsed = parseDay5(sampleInput) + val newseeds = + parsed.seeds.chunked(2).map { + it[0] until it[0] + it[1] + } + val locations = mutableSetOf() + + newseeds.forEach { longRange -> + var min: Long = Long.MAX_VALUE + for (seed in longRange) { + min = minOf(seedToLocation(seed, parsed, sampleInput), min) + locations.add(min) + } + } + val actual = locations.min() + assertEquals(expected, actual) + } + + private val sampleInput = + """ + seeds: 79 14 55 13 + + seed-to-soil map: + 50 98 2 + 52 50 48 + + soil-to-fertilizer map: + 0 15 37 + 37 52 2 + 39 0 15 + + fertilizer-to-water map: + 49 53 8 + 0 11 42 + 42 0 7 + 57 7 4 + + water-to-light map: + 88 18 7 + 18 25 70 + + light-to-temperature map: + 45 77 23 + 81 45 19 + 68 64 13 + + temperature-to-humidity map: + 0 69 1 + 1 0 69 + + humidity-to-location map: + 60 56 37 + 56 93 4 + """.trimIndent().split("\n") +} diff --git a/src/test/kotlin/day06/Day06KtTest.kt b/src/test/kotlin/day06/Day06KtTest.kt new file mode 100644 index 0000000..ec1e51b --- /dev/null +++ b/src/test/kotlin/day06/Day06KtTest.kt @@ -0,0 +1,40 @@ +package day06 + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class Day06KtTest { + @Test + fun parseRacesReturnsCorrectResult() { + val expected = listOf(Pair(7, 9), Pair(15, 40), Pair(30, 200)) + val actual = parseDay6(sampleInput) + assertEquals(expected, actual) + } + + @Test + fun parseRacesReturnsCorrectResultP2() { + val expected = Pair(71530L, 940200L) + val actual = parseDay6P2(sampleInput) + assertEquals(expected, actual) + } + + @Test + fun findBestSpeedReturnsCorrectResult() { + val expected = listOf(2, 3, 4, 5) + val actual = findBestSpeeds(parseDay6(sampleInput).first()) + assertEquals(expected, actual) + } + + @Test + fun part2ReturnsCorrectResult() { + val expected = 71503 + val actual = part2(sampleInput) + assertEquals(expected, actual) + } + + private val sampleInput = + """ + Time: 7 15 30 + Distance: 9 40 200 + """.trimIndent().split("\n") +}