204 lines
7.2 KiB
Kotlin

package aoc2023
import println
import readInput
import splitLongs
/*
--- Day 5: If You Give A Seed A Fertilizer ---
https://adventofcode.com/2023/day/5
*/
fun main() {
val inlineTestInput = """
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
50 98 2
52 50 48
"""
fun part1(input: List<String>): Long {
val typeMap = mapOf(
"seed" to 0,
"soil" to 1,
"fertilizer" to 2,
"water" to 3,
"light" to 4,
"temperature" to 5,
"humidity" to 6
)
val mapOfMaps = HashMap<Int, MutableList<Pair<LongRange, Long>>>()
var seeds = LongArray(0)
var fromId = 0
for (i in input) {
if (i.contains(":")) {
val (type, data) = i.split(":")
when (type) {
"seeds" -> seeds = data.splitLongs().toLongArray()
else -> {
val from = type.split("-")[0]
fromId = typeMap[from]!!
}
}
} else if (i.isNotBlank()) {
val (destRangeStart, sourceRangeStart, length) = i.splitLongs()
val rangeLists = mapOfMaps.getOrPut(fromId) { ArrayList() }
rangeLists.add(LongRange(sourceRangeStart, sourceRangeStart + length - 1) to destRangeStart)
}
}
val results = seeds.map {
var dest = it
for (i in 0..6) {
val rangeLists = mapOfMaps[i]!!
for (r in rangeLists) {
if (dest in r.first) {
dest = r.second + (dest - r.first.start)
break
}
}
}
dest
}
return results.min()
}
fun part2(input: List<String>): Long {
val typeMap = mapOf(
"seed" to 0,
"soil" to 1,
"fertilizer" to 2,
"water" to 3,
"light" to 4,
"temperature" to 5,
"humidity" to 6
)
val mapOfMaps = HashMap<Int, MutableList<Pair<LongRange, Long>>>()
var seeds = ArrayDeque<LongRange>()
var fromId = 0
for (i in input) {
if (i.contains(":")) {
val (type, data) = i.split(":")
when (type) {
"seeds" -> seeds = ArrayDeque(
data.splitLongs().chunked(2)
.map { LongRange(it[0], it[0] + it[1] - 1) })
else -> {
val from = type.split("-")[0]
fromId = typeMap[from]!!
}
}
} else if (i.isNotBlank()) {
val (destRangeStart, sourceRangeStart, length) = i.splitLongs()
val rangeLists = mapOfMaps.getOrPut(fromId) { ArrayList() }
rangeLists.add(LongRange(sourceRangeStart, sourceRangeStart + length - 1) to destRangeStart)
}
}
for (i in 0..6) {
mapOfMaps[i]!!.sortBy { it.first.first }
}
var sourceRanges = ArrayDeque(seeds.sortedBy { it.first })
for (i in 0..6) {
val newRanges = ArrayDeque<LongRange>()
val rangeLists = mapOfMaps[i]!!
while (sourceRanges.isNotEmpty()) {
val sourceRange = sourceRanges.removeFirst()
var found = false
for ((mapRange, destPos) in rangeLists) {
if (sourceRange.first in mapRange && sourceRange.last in mapRange) {
val dest = destPos + (sourceRange.first - mapRange.first)
val destLength = sourceRange.last + 1 - sourceRange.first
newRanges.add(LongRange(dest, dest + destLength - 1))
found = true
break
} else if (sourceRange.first in mapRange) {
val dest = destPos + (sourceRange.first - mapRange.first)
val destLength = mapRange.last + 1 - sourceRange.first
newRanges.add(LongRange(dest, dest + destLength - 1))
val newSource = mapRange.last + 1
val newSourceLength = sourceRange.last - mapRange.last
sourceRanges.addFirst(LongRange(newSource, newSource + newSourceLength - 1))
found = true
break
} else if (sourceRange.last in mapRange) {
val destLength = sourceRange.last + 1 - mapRange.first
val dest = destPos
newRanges.add(LongRange(dest, dest + destLength - 1))
val newSource = sourceRange.first
val newSourceLength = mapRange.first - sourceRange.first
sourceRanges.addFirst(LongRange(newSource, newSource + newSourceLength - 1))
found = true
break
// funnily, this is unused, but it should also be a possibility
// } else if (sourceRange.first < mapRange.first && sourceRange.last > mapRange.last) {
// val destLength = mapRange.last + 1 - mapRange.first
// val dest = destPos + mapRange.first - sourceRange.first
// newRanges.add(LongRange(dest, dest + destLength - 1))
// val newSource1 = mapRange.first
// val newSource1Length = mapRange.first - sourceRange.first - 1
// sourceRanges.addFirst(LongRange(newSource1, newSource1 + newSource1Length - 1))
// val newSource2 = mapRange.last + 1
// val newSource2Length = sourceRange.last - mapRange.last + 1
// sourceRanges.addFirst(LongRange(newSource2, newSource2 + newSource2Length - 1))
// found = true
// break
}
//if (mapRange.first > sourceRange.first) break
}
if (!found) {
newRanges.add(sourceRange)
}
}
sourceRanges = ArrayDeque(newRanges.sortedBy { it.first }.distinct())
}
return sourceRanges.minOf { it.first }
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2023/Day05_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 35L)
check(testInputPart2Result == 46L)
val input = readInput("aoc2023/Day05")
part1(input).println()
part2(input).println()
}