Skip to content

Commit 4add4e9

Browse files
committed
Solution 2018-07 (The Sum of Its Parts)
1 parent 6948418 commit 4add4e9

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package de.ronny_h.aoc.year2018.day07
2+
3+
import de.ronny_h.aoc.extensions.asList
4+
import de.ronny_h.aoc.year2018.day07.TheSumOfItsParts.Companion.createDependencyGraph
5+
import de.ronny_h.aoc.year2018.day07.TheSumOfItsParts.Companion.simulateSteps
6+
import io.kotest.core.spec.style.StringSpec
7+
import io.kotest.matchers.shouldBe
8+
9+
class TheSumOfItsPartsTest : StringSpec({
10+
11+
val input = """
12+
Step C must be finished before step A can begin.
13+
Step C must be finished before step F can begin.
14+
Step A must be finished before step B can begin.
15+
Step A must be finished before step D can begin.
16+
Step B must be finished before step E can begin.
17+
Step D must be finished before step E can begin.
18+
Step F must be finished before step E can begin.
19+
""".asList()
20+
21+
"Input can be parsed" {
22+
input.parseSteps() shouldBe listOf(
23+
"C" to "A",
24+
"C" to "F",
25+
"A" to "B",
26+
"A" to "D",
27+
"B" to "E",
28+
"D" to "E",
29+
"F" to "E",
30+
)
31+
}
32+
33+
"part 1: the order the steps in the instructions should be completed" {
34+
TheSumOfItsParts().part1(input) shouldBe "CABDFE"
35+
}
36+
37+
"simulate steps with two workers" {
38+
input.createDependencyGraph().simulateSteps(2, 0) shouldBe "15"
39+
}
40+
41+
"part 2: the number of seconds it takes with 5 workers" {
42+
TheSumOfItsParts().part2(input) shouldBe "253"
43+
}
44+
})

0 commit comments

Comments
 (0)