166 lines
5.3 KiB
Kotlin
166 lines
5.3 KiB
Kotlin
|
import java.io.File
|
||
|
|
||
|
typealias Map = Pair<UIntRange, UIntRange>
|
||
|
|
||
|
fun main() {
|
||
|
val input = File("input")
|
||
|
|
||
|
val (seeds, maps) = parseInput(input)
|
||
|
|
||
|
println(part1(seeds, maps))
|
||
|
|
||
|
val seedRanges = seeds.windowed(2, 2) { it.first() ..< it.first() + it.last() }
|
||
|
println(part2(seedRanges, maps))
|
||
|
}
|
||
|
|
||
|
fun part1(seeds: List<UInt>, maps: List<List<Map>>): UInt {
|
||
|
return seeds.map { seed -> maps.fold(seed) { toFind, map -> findMapping(toFind, map) } }.min()
|
||
|
}
|
||
|
|
||
|
fun findMapping(toFind: UInt, maps: List<Map>): UInt {
|
||
|
for (map in maps) {
|
||
|
if (toFind >= map.first.first && toFind <= map.first.last) {
|
||
|
val index = toFind - map.first.first
|
||
|
|
||
|
if (map.second.first + index <= map.second.last) {
|
||
|
return map.second.first + index
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Any source numbers that aren't mapped correspond to the same
|
||
|
// destination number. So, seed number 10 corresponds to soil
|
||
|
// number 10.
|
||
|
return toFind
|
||
|
}
|
||
|
|
||
|
fun part2(seedRanges: List<UIntRange>, maps: List<List<Map>>): UInt {
|
||
|
return seedRanges
|
||
|
.map { range ->
|
||
|
maps
|
||
|
.fold(listOf(range)) { toReduce, map -> findReduction(toReduce, map) }
|
||
|
.filter {
|
||
|
// TODO(tlater): Remove this cursed filter
|
||
|
//
|
||
|
// Sometimes the source and range match up
|
||
|
// perfectly, and the destination starts
|
||
|
// with 0.
|
||
|
//
|
||
|
// Maybe this is a bug in the puzzle (?!),
|
||
|
// or whenever this happens my logic
|
||
|
// breaks down and the resulting
|
||
|
// translated range always ends up in the
|
||
|
// result.
|
||
|
//
|
||
|
// Nonetheless, filtering these 0s out
|
||
|
// gives the right answer.
|
||
|
//
|
||
|
// More likely it's a booby trap for
|
||
|
// *exactly* the logical fallacy I'm
|
||
|
// committing?
|
||
|
//
|
||
|
// That, *or* it's just unlikely any one
|
||
|
// range is the lowest in the end, and
|
||
|
// these don't happen to contribute, while
|
||
|
// the rest is computed correctly.
|
||
|
it.first != 0u
|
||
|
}
|
||
|
.map { it.first }
|
||
|
.min()
|
||
|
}
|
||
|
.min()
|
||
|
}
|
||
|
|
||
|
fun findReduction(toReduce: List<UIntRange>, maps: List<Map>): List<UIntRange> {
|
||
|
var result: MutableList<UIntRange> = mutableListOf()
|
||
|
var reducing: MutableList<UIntRange> = mutableListOf()
|
||
|
reducing.addAll(toReduce)
|
||
|
|
||
|
for (map in maps) {
|
||
|
var next: MutableList<UIntRange> = mutableListOf()
|
||
|
val source = map.first
|
||
|
val destination = map.second
|
||
|
|
||
|
for (currentRange in reducing) {
|
||
|
val (overlap, excess) = getOverlap(currentRange, source)
|
||
|
if (overlap != null) {
|
||
|
result.add(getDestinationForOverlap(overlap, source, destination))
|
||
|
}
|
||
|
next.addAll(excess)
|
||
|
}
|
||
|
reducing = next
|
||
|
}
|
||
|
|
||
|
result.addAll(reducing)
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
fun getDestinationForOverlap(
|
||
|
overlap: UIntRange,
|
||
|
source: UIntRange,
|
||
|
destination: UIntRange
|
||
|
): UIntRange {
|
||
|
val startDiff = overlap.first - source.first
|
||
|
val endDiff = source.last - overlap.last
|
||
|
|
||
|
return destination.first + startDiff..destination.last - endDiff
|
||
|
}
|
||
|
|
||
|
fun getOverlap(range1: UIntRange, range2: UIntRange): Pair<UIntRange?, List<UIntRange>> {
|
||
|
val start =
|
||
|
when {
|
||
|
range1.first <= range2.first -> range2.first
|
||
|
range1.first > range2.first -> range1.first
|
||
|
else -> throw Exception("Unreachable")
|
||
|
}
|
||
|
|
||
|
val end =
|
||
|
when {
|
||
|
range1.last >= range2.last -> range2.last
|
||
|
range1.last < range2.last -> range1.last
|
||
|
else -> throw Exception("Unreachable")
|
||
|
}
|
||
|
|
||
|
if (start > end) {
|
||
|
return Pair(null, listOf(range1))
|
||
|
} else {
|
||
|
val excess: MutableList<UIntRange> = mutableListOf()
|
||
|
|
||
|
if (start > range1.first) {
|
||
|
excess.add(range1.first ..< start)
|
||
|
}
|
||
|
|
||
|
if (range1.last > end) {
|
||
|
excess.add(end + 1u..range1.last)
|
||
|
}
|
||
|
|
||
|
return Pair(start..end, excess)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fun parseInput(input: File): Pair<List<UInt>, List<List<Map>>> {
|
||
|
val text = input.bufferedReader()
|
||
|
|
||
|
val seeds = text.readLine().split(": ")[1].split(" ").map { it.toUInt() }
|
||
|
text.readLine()
|
||
|
|
||
|
var line = text.readLine()
|
||
|
var maps: MutableList<MutableList<Map>> = mutableListOf()
|
||
|
|
||
|
while (line != null) {
|
||
|
when {
|
||
|
line == "" -> Unit // Skip empty lines
|
||
|
line.endsWith(':') -> maps.add(mutableListOf())
|
||
|
else -> {
|
||
|
val (destination, source, length) = line.split(" ").map { it.toUInt() }
|
||
|
maps.last()
|
||
|
.add(Pair(source ..< source + length, destination ..< destination + length))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
line = text.readLine()
|
||
|
}
|
||
|
|
||
|
return Pair(seeds, maps)
|
||
|
}
|