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): 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>>() 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): 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>>() var seeds = ArrayDeque() 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() 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() }