generated from Jadarma/advent-of-code-kotlin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathY2024D07.kt
69 lines (57 loc) · 2.9 KB
/
Y2024D07.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package aockt.y2024
import aockt.util.parse
import aockt.y2024.Y2024D07.Operator.*
import io.github.jadarma.aockt.core.Solution
object Y2024D07 : Solution {
/** Possible operators for the calibration equations. */
private enum class Operator { Add, Multiply, Concatenate }
/** Undoes an operator by applying the opposite. Unsafe calls throw. */
private fun Operator.undo(a: Long, b: Long): Long = when(this) {
Add -> a - b
Multiply -> a / b
Concatenate -> a.toString().removeSuffix(b.toString()).toLong()
}
/** The numbers of a bridge repair calibration equation, with missing operators. */
private data class Equation(val result: Long, val operands: List<Long>) {
init {
require(operands.size >= 2) { "Equation must have at least two operands." }
}
/** Determines if this equation has at least one solution using the given [operators]. */
fun isSolvable(operators: Set<Operator>): Boolean {
// DFS + pruning: Solve equation backwards, removing candidate operators that can't apply:
// - Can't be Add if subtracting would lead to negative numbers.
// - Can't be Multiply if division is not exact, or divisor is zero.
// - Can't be Concatenate if the number isn't a (strictly shorter) suffix of the other.
// When reaching the last number, the equation succeeds if it is equal to the accumulator.
fun recurse(acc: Long, index: Int): Boolean {
if (index == 0) return acc == operands.first()
val number = operands[index]
return operators
.asSequence()
.filterNot { it == Add && acc < number }
.filterNot { it == Multiply && number == 0L }
.filterNot { it == Multiply && acc % number != 0L }
.filterNot { it == Concatenate && acc == number }
.filterNot { it == Concatenate && !acc.toString().endsWith(number.toString())}
.any { recurse(it.undo(acc, number), index - 1) }
}
return recurse(result, operands.lastIndex)
}
}
/** Parse the [input] and return the list of equations. */
private fun parseInput(input: String): List<Equation> = parse {
input
.lines()
.map { line ->
val (result, operands) = line.split(": ", limit = 2)
Equation(result.toLong(), operands.split(' ').map(String::toLong))
}
}
/** Common solution to both parts. */
private fun solve(input: String, vararg operators: Operator): Long =
parseInput(input)
.filter { it.isSolvable(operators.toSet()) }
.sumOf { it.result }
override fun partOne(input: String) = solve(input, Add, Multiply)
override fun partTwo(input: String) = solve(input, Add, Multiply, Concatenate)
}