Advent of Code in Kotlin 2021 - Day 7
This is part 7 of a series, so if you haven't read the previous parts, start here.
For today's puzzle we need to help a swarm of crabs to converge on a single position as efficiently as possible. We are given a number of positions and need to figure out where they should all converge.
For part 1, the total movement of all crabs should be minimized. Reading other solutions, I realized you could just use the median position here, but I just iterated over all positions between the smallest and largest starting position.
private fun targetOptions(startingLocations: List<Int>) = min(startingLocations)..max(startingLocations)
Now we need a mapping function to calculate how far each crab has to move to get to a target.
private fun distances(startingLocations: List<Int>, target: Int) = startingLocations.map { abs(it - target) }
Using these two helpers, we can complete part 1:
private fun part1(startingLocations: List<Int>) = targetOptions(startingLocations)
.map { distances(startingLocations, it).sum() }
.minOrNull()
For part 2, the cost of movement is no longer equal to the distance, but the triangle number of the distance (summing all numbers from 1 up to the target number). Let's define another helper:
private fun advancedFuelConsumption(distance: Int) = (distance * (distance + 1)) / 2
Now we've got part 2 sorted.
private fun part2(startingLocations: List<Int>) = targetOptions(startingLocations)
.map { distances(startingLocations, it).sumOf { distance -> advancedFuelConsumption(distance) } }
.minOrNull()
Let's clean it up a little more though and define a higher order function for solving this. Rather than convert the distance to fuel cost inline, we pass in a function to do the conversion, which is just the identity function for part 1.
Here's the complete final listing:
fun main() {
val startingLocations = inputText(2021, 7).split(",").map(String::toInt)
println(calculateOptimalFuelCost(startingLocations, ::simpleFuelConsumption))
println(calculateOptimalFuelCost(startingLocations, ::advancedFuelConsumption))
}
private fun calculateOptimalFuelCost(startingLocations: List<Int>, fuelCostFunction: (distance: Int) -> Int) =
targetOptions(startingLocations)
.map { target -> distances(startingLocations, target) }
.map { distances -> distances.sumOf(fuelCostFunction) }
.minOrNull()
private fun targetOptions(startingLocations: List<Int>) = min(startingLocations)..max(startingLocations)
private fun distances(startingLocations: List<Int>, target: Int) = startingLocations.map { abs(it - target) }
private fun simpleFuelConsumption(distance: Int) = distance
private fun advancedFuelConsumption(distance: Int) = (distance * (distance + 1)) / 2
That's day 7 done - two more stars in the bag.