Skip to content

Commit 80b89f5

Browse files
committed
day 14, part 2 solution
1 parent 0ea69e1 commit 80b89f5

File tree

1 file changed

+103
-14
lines changed
  • day14/src/main/kotlin/org/adventofcode

1 file changed

+103
-14
lines changed

day14/src/main/kotlin/org/adventofcode/App.kt

Lines changed: 103 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,124 @@ import java.io.File
44

55
val RULE_REGEX_PATTERN = """([\w]+) -> ([\w])""".toRegex()
66

7-
fun runSolutionPart1(template: String, rules: Map<String, String>) {
8-
println("Day 14 Solution: Part 1 (Basic Implementation)")
9-
val steps = 10
7+
typealias InsertionRules = Map<String, Char>
8+
typealias CacheKey = Pair<String, Int>
9+
typealias CharCountMap = Map<Char, Long>
10+
typealias Cache = Map<CacheKey, CharCountMap>
11+
12+
fun CharCountMap.sort(): List<Pair<Char, Long>> {
13+
return this.toList().sortedWith { a, b ->
14+
when (val countCompare = a.second.compareTo(b.second)) {
15+
0 -> a.first.compareTo(b.first)
16+
else -> countCompare
17+
}
18+
}
19+
}
20+
21+
object PolymerProcessor {
22+
private fun processPolymer(
23+
polymer: String,
24+
rules: InsertionRules,
25+
step: Int,
26+
cache: Cache,
27+
): Pair<CharCountMap, Cache> {
28+
val (nextCharCountMap, cacheResult) = polymer
29+
.windowedSequence(2)
30+
.fold(mapOf<Char, Long>() to cache) { (thisAggregate, thisCache), nextPair ->
31+
val (result, nextCache) = processElementPair(nextPair, rules, step, thisCache)
32+
val nextAggregate = result.toList().fold(thisAggregate) { agg, (char, count) ->
33+
agg + (char to (agg[char]?.let { it + count } ?: count))
34+
}
35+
nextAggregate to nextCache
36+
}
37+
val overlappingElements = polymer.substring(1, polymer.lastIndex)
38+
val charCountMapResult = overlappingElements
39+
.fold(nextCharCountMap) { map, char ->
40+
var count = map[char]?.let { it - 1 } ?: 1
41+
map + (char to count)
42+
}
43+
return charCountMapResult to cacheResult
44+
}
45+
46+
private fun processElementPair(
47+
pair: String,
48+
rules: InsertionRules,
49+
step: Int,
50+
cache: Cache,
51+
): Pair<CharCountMap, Cache> {
52+
val cachedValue = cache[pair to step]
53+
if (cachedValue != null) {
54+
return cachedValue to cache
55+
}
56+
57+
if (step == 0) {
58+
val expandedResult = pair.toList().fold(mapOf<Char, Long>()) { map, char ->
59+
map + (char to map.getOrDefault(char, 0L) + 1L)
60+
}
61+
val nextCache = cache + (Pair(pair, step) to expandedResult)
62+
return expandedResult to nextCache
63+
}
64+
65+
val insertedElem = rules[pair] ?: throw error("No insertion rule for pair $pair")
66+
val polymer = "${pair[0]}$insertedElem${pair[1]}"
67+
val (aggregateResult, nextCache) = processPolymer(polymer, rules, step - 1, cache)
68+
val cacheResult = nextCache + (Pair(pair, step) to aggregateResult)
69+
70+
return aggregateResult to cacheResult
71+
}
72+
73+
fun process(template: String, rules: InsertionRules, steps: Int): CharCountMap {
74+
val cache = mapOf<CacheKey, CharCountMap>()
75+
val result = processPolymer(template, rules, steps, cache)
76+
return result.first
77+
}
78+
}
79+
80+
fun summarizePolymerProcessingResult(result: CharCountMap) {
81+
val sortedCharCounts = result.sort()
82+
val leastCommon = sortedCharCounts.first()
83+
val mostCommon = sortedCharCounts.last()
84+
val score = mostCommon.second - leastCommon.second
85+
86+
println("Sort char-count: $sortedCharCounts")
87+
println("Most common char count: $leastCommon")
88+
println("Least common char count: $mostCommon")
89+
println("Score: $score")
90+
}
91+
92+
fun runSolutionPart1(template: String, rules: Map<String, Char>, steps: Int = 10) {
93+
println("Day 14 Solution: Part 1 (Basic Implementation)\n")
1094
val polymer = (0 until steps).fold(template) { polymer, _ ->
1195
polymer
1296
.windowedSequence(2)
1397
.mapNotNull { rules[it]?.let { elem -> "${it[0]}$elem${it[1]}" } }
1498
.mapIndexed { idx, chunk -> if (idx == 0) chunk else chunk.substring(1) }
1599
.joinToString("")
16100
}
17-
val charCounts = polymer.asSequence().fold(mapOf<Char, Int>()) { map, char ->
18-
map + (char to map.getOrDefault(char, 0) + 1)
101+
val charCounts: CharCountMap = polymer.asSequence().fold(mapOf<Char, Long>()) { map, char ->
102+
map + (char to map.getOrDefault(char, 0L) + 1L)
19103
}
20-
val mostCommonCharCount = charCounts.maxOf { (_, count) -> count }
21-
val leastCommonCharCount = charCounts.minOf { (_, count) -> count }
22-
val score = mostCommonCharCount - leastCommonCharCount
23104

24-
println("Most common char count: $mostCommonCharCount")
25-
println("Least common char count: $leastCommonCharCount")
26-
println("Score: $score")
105+
summarizePolymerProcessingResult(charCounts)
106+
}
107+
108+
fun runSolutionPart2(template: String, rules: Map<String, Char>, steps: Int = 10) {
109+
println("Day 14 Solution: Part 2\n")
110+
val result = PolymerProcessor.process(template, rules, steps)
111+
summarizePolymerProcessingResult(result)
27112
}
28113

29114
fun main() {
30-
val input = File("day14/src/main/resources/puzzleInput.txt").readLines()
115+
val input = File("day14/src/main/resources/sampleInput.txt").readLines()
31116
val template = input.first()
32117
val rules = input.drop(2).mapNotNull {
33118
val result = RULE_REGEX_PATTERN.matchEntire(it)
34-
result?.groupValues?.let { (_, fromPair, toChar) -> fromPair to toChar }
119+
result?.groupValues?.let { (_, fromPair, toChar) -> fromPair to toChar[0] }
35120
}.toMap()
36121

37-
runSolutionPart1(template, rules)
122+
runSolutionPart1(template, rules, 10)
123+
println()
124+
runSolutionPart2(template, rules, 10)
125+
126+
38127
}

0 commit comments

Comments
 (0)