|
| 1 | +package de.ronny_h.aoc.year2018.day07 |
| 2 | + |
| 3 | +import de.ronny_h.aoc.AdventOfCode |
| 4 | + |
| 5 | +fun main() = TheSumOfItsParts().run("BKCJMSDVGHQRXFYZOAULPIEWTN", "1040") |
| 6 | + |
| 7 | +class TheSumOfItsParts : AdventOfCode<String>(2018, 7) { |
| 8 | + override fun part1(input: List<String>): String = input |
| 9 | + .createDependencyGraph() |
| 10 | + .executeStepsInDependencyOrder() |
| 11 | + .joinToString("") |
| 12 | + |
| 13 | + override fun part2(input: List<String>): String = input |
| 14 | + .createDependencyGraph() |
| 15 | + .simulateSteps(5, 60) |
| 16 | + |
| 17 | + companion object { |
| 18 | + fun List<String>.createDependencyGraph(): MutableMap<String, MutableSet<String>> { |
| 19 | + // build a graph using a map: step -> list of steps that must be finished before |
| 20 | + val dependencyGraph = mutableMapOf<String, MutableSet<String>>() |
| 21 | + parseSteps().forEach { |
| 22 | + dependencyGraph.computeIfAbsent(it.second) { mutableSetOf() }.add(it.first) |
| 23 | + } |
| 24 | + return dependencyGraph |
| 25 | + } |
| 26 | + |
| 27 | + private fun Map<String, Set<String>>.executeStepsInDependencyOrder(): List<String> { |
| 28 | + // Find the first steps that must have no other dependencies. |
| 29 | + // ready steps = not done steps whose dependant steps are done |
| 30 | + var readySteps = values.flatten().filterNot { it in keys }.toSet() |
| 31 | + val stepsDone = mutableListOf<String>() |
| 32 | + while (readySteps.isNotEmpty()) { |
| 33 | + val step = readySteps.min() |
| 34 | + stepsDone.add(step) |
| 35 | + readySteps = (readySteps - step) + this |
| 36 | + .filter { it.key !in stepsDone } |
| 37 | + .filter { it.allDependenciesAreDone(stepsDone) } |
| 38 | + .keys |
| 39 | + } |
| 40 | + return stepsDone |
| 41 | + } |
| 42 | + |
| 43 | + fun Map<String, Set<String>>.simulateSteps(workers: Int, baseDurationSeconds: Int): String { |
| 44 | + val stepsDone = mutableListOf<String>() |
| 45 | + val readySteps = values.flatten().filterNot { it in keys }.toMutableSet() |
| 46 | + var workersOccupied = 0 |
| 47 | + var completionSeconds = -1 |
| 48 | + var stepsInAction = listOf<StepInAction>() |
| 49 | + |
| 50 | + while (readySteps.isNotEmpty() || stepsInAction.isNotEmpty()) { |
| 51 | + completionSeconds++ |
| 52 | + stepsInAction.forEach { it.remainingDuration-- } |
| 53 | + val (done, stillInAction) = stepsInAction.partition { it.remainingDuration == 0 } |
| 54 | + stepsDone.addAll(done.map(StepInAction::name)) |
| 55 | + |
| 56 | + workersOccupied -= done.size |
| 57 | + |
| 58 | + readySteps += this.filter { it.key !in (stepsDone + stepsInAction.map(StepInAction::name)) } |
| 59 | + .filter { it.allDependenciesAreDone(stepsDone) } |
| 60 | + .keys |
| 61 | + val newSteps = readySteps.sorted().take(workers - workersOccupied).toSet() |
| 62 | + workersOccupied += newSteps.size |
| 63 | + stepsInAction = stillInAction + newSteps.map { StepInAction(it, it.duration(baseDurationSeconds)) } |
| 64 | + readySteps -= newSteps |
| 65 | + } |
| 66 | + return "$completionSeconds" |
| 67 | + } |
| 68 | + |
| 69 | + private data class StepInAction(val name: String, var remainingDuration: Int) |
| 70 | + |
| 71 | + private fun Map.Entry<String, Set<String>>.allDependenciesAreDone(stepsDone: List<String>): Boolean = |
| 72 | + value.all { it in stepsDone } |
| 73 | + |
| 74 | + // duration = baseDuration + letter's position in the alphabet |
| 75 | + private fun String.duration(baseDurationSeconds: Int) = first().code - 'A'.code + baseDurationSeconds + 1 |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +private val stepPattern = """Step ([A-Z]) must be finished before step ([A-Z]) can begin.""".toPattern() |
| 80 | + |
| 81 | +fun List<String>.parseSteps() = map { |
| 82 | + val matcher = stepPattern.matcher(it) |
| 83 | + require(matcher.matches()) |
| 84 | + matcher.group(1) to matcher.group(2) |
| 85 | +} |
0 commit comments