44package day05
55
66import common.day
7- import common.middle
7+ import common.collectionops. middle
88import common.readInput
99
1010typealias OrderingRules = Map <Int , Set <Int >>
1111typealias Updates = List <Int >
1212
1313/* *
14- * We parse the input failure rules into two distinct structures.
15- * The entries are of the form a|b, where if b occurs before a in a list of updates,
16- * the process should fail.
17- *
18- * Thus, this is parsed into a Map<Int, Set<Int>> where all the b from above comprise keys,
19- * and any entry a1|b, a2|b, etc. form the set of values {a1, a2, ...} indicate that if an
20- * ai occurs after b, the updates fail.
14+ * Parse the violation rules into a map where:
15+ * - Keys are the integers `b` that must not precede certain `a` values.
16+ * - Values are sets of `a` values that cause a violation if they follow the key.
2117 */
2218private fun parseViolationRules (input : String ): OrderingRules =
2319 input.lines()
@@ -26,69 +22,87 @@ private fun parseViolationRules(input: String): OrderingRules =
2622 val (a, b) = line.trim().split(' |' ).map(String ::toInt)
2723 b to a
2824 }
29- .groupBy(Pair <Int , Int >::first, Pair <Int , Int >::second)
30- .mapValues { (_, b) -> b.toSet() }
31-
25+ .groupBy({ it.first }, { it.second })
26+ .mapValues { (_, values) -> values.toSet() }
3227
28+ /* *
29+ * Parse updates as lists of integers from input lines.
30+ */
3331private fun parseUpdates (input : String ): List <Updates > =
34- input.lines()
35- .map { line -> line.trim().split(' ,' ).map(String ::toInt) }
32+ input.lines().map { line ->
33+ line.trim().split(' ,' ).map(String ::toInt)
34+ }
3635
37- fun parseViolation (input : String ): Pair <OrderingRules , List <Updates >> {
36+ /* *
37+ * Parse the violation rules and updates from the input string.
38+ * The input is split into two sections separated by a blank line.
39+ */
40+ private fun parseViolation (input : String ): Pair <OrderingRules , List <Updates >> {
3841 val (rulesString, updatesString) = input.split(" \n\n " )
39- val rules = parseViolationRules(rulesString)
40- val updates = parseUpdates(updatesString)
41- return rules to updates
42+ return parseViolationRules(rulesString) to parseUpdates(updatesString)
4243}
4344
44- fun passesViolation (updates : Updates , violationRules : OrderingRules ): Boolean =
45- updates.fold(emptySet<Int >()) { disallowed, page ->
45+ /* *
46+ * Check if a given sequence of updates passes the violation rules.
47+ * Returns `true` if no violations occur, otherwise `false`.
48+ */
49+ private fun passesViolation (updates : Updates , violationRules : OrderingRules ): Boolean {
50+ val disallowed = mutableSetOf<Int >()
51+ for (page in updates) {
4652 if (page in disallowed) return false
47- disallowed + (violationRules[page] ? : emptySet())
48- }.let { true }
53+ disallowed + = violationRules[page] ? : emptySet()
54+ }
55+ return true
56+ }
4957
5058/* *
51- * Idea:
52- * Gather all elements.
53- * While there are still elements in the remaining element set:
54- * Keep picking the element that doesn't appear in any other violation for remaining elements
55- * and add it to the ordering.
56- * Remove it from the remaining element set.
59+ * Reorder the updates to satisfy violation rules.
60+ * Uses a recursive approach to build the ordering.
5761 */
58- fun reorder (updates : Updates , violationRules : OrderingRules ): List < Int > {
62+ private fun reorder (updates : Updates , violationRules : OrderingRules ): Updates {
5963 tailrec fun aux (
60- reorder : List < Int > = emptyList(),
64+ reordered : Updates = emptyList(),
6165 remaining : Set <Int > = updates.toSet()
6266 ): Updates {
63- if (remaining.isEmpty()) return reorder
64- val disallowed = remaining.flatMap { violationRules[it] ? : emptySet() }
67+ if (remaining.isEmpty()) return reordered
68+ val disallowed = remaining.flatMap { violationRules[it] ? : emptySet() }.toSet()
6569 val candidates = remaining - disallowed
66- if (candidates.isEmpty()) throw RuntimeException ( " No candidate for reordering. " )
67- val candidate = candidates.first( )
68- return aux(reorder + candidate, remaining - candidate)
70+ val candidate = candidates.firstOrNull( )
71+ ? : throw RuntimeException ( " No candidate for reordering. " )
72+ return aux(reordered + candidate, remaining - candidate)
6973 }
7074 return aux()
7175}
7276
73- fun answer1 (violationRules : OrderingRules , updatesList : List <Updates >): Int =
74- updatesList.filter { update -> passesViolation(update, violationRules) }
75- .sumOf(List <Int >::middle)
76-
77- fun answer2 (violationRules : OrderingRules , updatesList : List <Updates >): Int =
78- updatesList.filterNot { passesViolation(it, violationRules) }
79- .map { reorder(it, violationRules) }
80- .sumOf(List <Int >::middle)
77+ /* *
78+ * Part 1: Sum the "middle" values of updates that pass the violation rules.
79+ */
80+ fun answer1 (input : String ): Int =
81+ parseViolation(input).let { (violationRules, updatesList) ->
82+ updatesList
83+ .filter { passesViolation(it, violationRules) }
84+ .sumOf(List <Int >::middle)
85+ }
8186
87+ /* *
88+ * Part 2: Sum the "middle" values of reordered updates that fail the violation rules.
89+ */
90+ fun answer2 (input : String ): Int =
91+ parseViolation(input).let { (violationRules, updatesList) ->
92+ updatesList
93+ .filterNot { passesViolation(it, violationRules) }
94+ .map { reorder(it, violationRules) }
95+ .sumOf(List <Int >::middle)
96+ }
8297
8398fun main () {
8499 val input = readInput({}::class .day()).trim()
85- val (violationRules, updateList) = parseViolation(input)
86100
87101 println (" --- Day 5: Print Queue ---" )
88102
89- // Answer 1: 4281
90- println (" Part 1: ${answer1(violationRules, updateList )} " )
103+ // Part 1: 4281
104+ println (" Part 1: ${answer1(input )} " )
91105
92- // Answer 2: 5466
93- println (" Part 2: ${answer2(violationRules, updateList )} " )
94- }
106+ // Part 2: 5466
107+ println (" Part 2: ${answer2(input )} " )
108+ }
0 commit comments