204 lines
7.2 KiB
Kotlin
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()
|
|
}
|