day(18): add solution
Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
parent
90e1bfc107
commit
3701f6ffdf
3 changed files with 414 additions and 0 deletions
212
src/year2021/day18/Day18.kt
Normal file
212
src/year2021/day18/Day18.kt
Normal file
|
@ -0,0 +1,212 @@
|
|||
package year2021.day18
|
||||
|
||||
import readInput
|
||||
|
||||
enum class SnailfishNumberType {
|
||||
Regular,
|
||||
Pair
|
||||
}
|
||||
|
||||
abstract class SpecialNumber : Cloneable {
|
||||
public abstract override fun clone(): SpecialNumber
|
||||
abstract fun explode(path: MutableList<SpecialNumber>): Pair<Boolean, SpecialNumber>
|
||||
abstract fun split(path: MutableList<SpecialNumber>): Pair<Boolean, SpecialNumber>
|
||||
|
||||
abstract val type: SnailfishNumberType
|
||||
abstract val magnitude: Long
|
||||
|
||||
operator fun plus(other: SpecialNumber): SpecialNumber = reduce(PairNumber(this.clone(), other.clone()))
|
||||
}
|
||||
|
||||
data class RegularNumber(var number: Long) : SpecialNumber() {
|
||||
override fun clone(): SpecialNumber = RegularNumber(number)
|
||||
override fun explode(path: MutableList<SpecialNumber>): Pair<Boolean, SpecialNumber> = false to this
|
||||
override fun split(path: MutableList<SpecialNumber>): Pair<Boolean, SpecialNumber> =
|
||||
if (number < 10)
|
||||
(false to this)
|
||||
else
|
||||
(true to PairNumber(
|
||||
RegularNumber(number / 2), RegularNumber(number - number / 2)
|
||||
))
|
||||
|
||||
override val type: SnailfishNumberType
|
||||
get() = SnailfishNumberType.Regular
|
||||
|
||||
override val magnitude: Long
|
||||
get() = number
|
||||
|
||||
override fun toString(): String = "$number"
|
||||
}
|
||||
|
||||
data class PairNumber(var left: SpecialNumber, var right: SpecialNumber) : SpecialNumber() {
|
||||
override fun clone(): SpecialNumber = PairNumber(left.clone(), right.clone())
|
||||
|
||||
private fun tryReduce(
|
||||
operation: (SpecialNumber, MutableList<SpecialNumber>) -> Pair<Boolean, SpecialNumber>,
|
||||
path: MutableList<SpecialNumber>
|
||||
): Pair<Boolean, SpecialNumber> {
|
||||
path.add(this)
|
||||
|
||||
val (reducedLeft, left) = operation(left, path)
|
||||
this.left = left
|
||||
if (reducedLeft) {
|
||||
path.removeLast()
|
||||
return true to this
|
||||
}
|
||||
|
||||
val (reducedRight, right) = operation(right, path)
|
||||
this.right = right
|
||||
|
||||
path.removeLast()
|
||||
return reducedRight to this
|
||||
}
|
||||
|
||||
override fun explode(path: MutableList<SpecialNumber>): Pair<Boolean, SpecialNumber> {
|
||||
path.add(this)
|
||||
|
||||
if (path.size > 4 && left.type == SnailfishNumberType.Regular && right.type == SnailfishNumberType.Regular) {
|
||||
val previous = predecessor(path)
|
||||
if (previous != null) {
|
||||
previous.number += left.magnitude
|
||||
}
|
||||
|
||||
val next = successor(path)
|
||||
if (next != null) {
|
||||
next.number += right.magnitude
|
||||
}
|
||||
|
||||
path.removeLast()
|
||||
return true to RegularNumber(0)
|
||||
}
|
||||
|
||||
path.removeLast()
|
||||
return tryReduce(SpecialNumber::explode, path)
|
||||
}
|
||||
|
||||
override fun split(path: MutableList<SpecialNumber>): Pair<Boolean, SpecialNumber> =
|
||||
tryReduce(SpecialNumber::split, path)
|
||||
|
||||
override val type: SnailfishNumberType
|
||||
get() = SnailfishNumberType.Pair
|
||||
|
||||
override val magnitude: Long
|
||||
get() = 3 * left.magnitude + 2 * right.magnitude
|
||||
|
||||
override fun toString(): String = "[$left,$right]"
|
||||
}
|
||||
|
||||
fun String.toSnailfishNumber(): SpecialNumber {
|
||||
fun parseNumber(fromIndex: Int): Pair<Int, SpecialNumber> {
|
||||
if (this[fromIndex] != '[') {
|
||||
val toIndex =
|
||||
(fromIndex until this.length).firstOrNull { this[it] == ',' || this[it] == ']' } ?: this.length
|
||||
|
||||
return toIndex to RegularNumber(this.slice(fromIndex until toIndex).toLong())
|
||||
}
|
||||
|
||||
check(this[fromIndex] == '[')
|
||||
|
||||
val (comma, left) = parseNumber(fromIndex + 1)
|
||||
check(this[comma] == ',')
|
||||
|
||||
val (toIndex, right) = parseNumber(comma + 1)
|
||||
check(this[toIndex] == ']')
|
||||
|
||||
return toIndex + 1 to PairNumber(left, right)
|
||||
}
|
||||
val (index, number) = parseNumber(0)
|
||||
check(index == this.length)
|
||||
return number
|
||||
}
|
||||
|
||||
fun predecessor(path: List<SpecialNumber>): RegularNumber? {
|
||||
var i = path.size - 1
|
||||
|
||||
while (i > 0) {
|
||||
if (path[i] === (path[i - 1] as PairNumber).right) {
|
||||
var node = (path[i - 1] as PairNumber).left
|
||||
|
||||
while (node !is RegularNumber) {
|
||||
node = (node as PairNumber).right
|
||||
}
|
||||
return node
|
||||
}
|
||||
i--
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun successor(path: List<SpecialNumber>): RegularNumber? {
|
||||
var i = path.size - 1
|
||||
|
||||
while (i > 0) {
|
||||
if (path[i] === (path[i - 1] as PairNumber).left) {
|
||||
var node = (path[i - 1] as PairNumber).right
|
||||
|
||||
while (node !is RegularNumber) {
|
||||
node = (node as PairNumber).left
|
||||
}
|
||||
return node
|
||||
}
|
||||
i--
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun reduce(number: SpecialNumber): SpecialNumber {
|
||||
var newNumber = number
|
||||
val stack = mutableListOf<SpecialNumber>()
|
||||
|
||||
while (true) {
|
||||
val (hasExploded, afterExplode) = newNumber.explode(stack)
|
||||
check(stack.isEmpty())
|
||||
if (hasExploded) {
|
||||
newNumber = afterExplode
|
||||
continue
|
||||
}
|
||||
|
||||
val (hasSplit, afterSplit) = newNumber.split(stack)
|
||||
check(stack.isEmpty())
|
||||
if (hasSplit) {
|
||||
newNumber = afterSplit
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return newNumber
|
||||
}
|
||||
|
||||
fun asSFNumber(number: Long): SpecialNumber = RegularNumber(number)
|
||||
fun asSFNumber(left: Long, right: Long): SpecialNumber = PairNumber(RegularNumber(left), RegularNumber(right))
|
||||
fun asSFNumber(left: SpecialNumber, right: Long): SpecialNumber = PairNumber(left, RegularNumber(right))
|
||||
fun asSFNumber(left: Long, right: SpecialNumber): SpecialNumber = PairNumber(RegularNumber(left), right)
|
||||
fun asSFNumber(left: SpecialNumber, right: SpecialNumber): SpecialNumber = PairNumber(left, right)
|
||||
|
||||
fun part1(input: List<String>): Long =
|
||||
input.map { it.toSnailfishNumber() }
|
||||
.reduce(SpecialNumber::plus)
|
||||
.magnitude
|
||||
|
||||
fun part2(input: List<String>): Long =
|
||||
input.map(String::toSnailfishNumber).let { numbers ->
|
||||
numbers.indices
|
||||
.flatMap { i -> numbers.indices.map { j -> i to j } }
|
||||
.filter { (i, j) -> i != j }.maxOf { (i, j) ->
|
||||
(numbers[i] + numbers[j]).magnitude
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val sample = readInput(18, "sample")
|
||||
val input = readInput(18, "input")
|
||||
|
||||
check(part1(sample) == 4140L)
|
||||
println(part1(input))
|
||||
|
||||
check(part2(sample) == 3993L)
|
||||
println(part2(input))
|
||||
}
|
192
src/year2021/day18/SnailfishNumberTest.kt
Normal file
192
src/year2021/day18/SnailfishNumberTest.kt
Normal file
|
@ -0,0 +1,192 @@
|
|||
package year2021.day18
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class SnailfishNumberTest {
|
||||
@Test
|
||||
fun toSnailfishNumber() {
|
||||
assertEquals(asSFNumber(1, 2), "[1,2]".toSnailfishNumber())
|
||||
assertEquals(
|
||||
asSFNumber(asSFNumber(1, 2), 3), "[[1,2],3]".toSnailfishNumber()
|
||||
)
|
||||
assertEquals(asSFNumber(9, asSFNumber(8, 7)), "[9,[8,7]]".toSnailfishNumber())
|
||||
assertEquals(asSFNumber(asSFNumber(1, 9), asSFNumber(8, 5)), "[[1,9],[8,5]]".toSnailfishNumber())
|
||||
assertEquals(
|
||||
asSFNumber(
|
||||
asSFNumber(
|
||||
asSFNumber(asSFNumber(1, 2), asSFNumber(3, 4)), asSFNumber(asSFNumber(5, 6), asSFNumber(7, 8))
|
||||
), 9
|
||||
), "[[[[1,2],[3,4]],[[5,6],[7,8]]],9]".toSnailfishNumber()
|
||||
)
|
||||
assertEquals(
|
||||
asSFNumber(
|
||||
asSFNumber(
|
||||
asSFNumber(9, asSFNumber(3, 8)), asSFNumber(asSFNumber(0, 9), 6)
|
||||
), asSFNumber(
|
||||
asSFNumber(asSFNumber(3, 7), asSFNumber(4, 9)), 3
|
||||
)
|
||||
), "[[[9,[3,8]],[[0,9],6]],[[[3,7],[4,9]],3]]".toSnailfishNumber()
|
||||
)
|
||||
assertEquals(
|
||||
asSFNumber(
|
||||
asSFNumber(
|
||||
asSFNumber(asSFNumber(1, 3), asSFNumber(5, 3)), asSFNumber(asSFNumber(1, 3), asSFNumber(8, 7))
|
||||
), asSFNumber(
|
||||
asSFNumber(asSFNumber(4, 9), asSFNumber(6, 9)), asSFNumber(asSFNumber(8, 2), asSFNumber(7, 3))
|
||||
)
|
||||
), "[[[[1,3],[5,3]],[[1,3],[8,7]]],[[[4,9],[6,9]],[[8,2],[7,3]]]]".toSnailfishNumber()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reduce() {
|
||||
assertEquals(
|
||||
"[[[[0,7],4],[[7,8],[6,0]]],[8,1]]".toSnailfishNumber(),
|
||||
reduce("[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]".toSnailfishNumber())
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]".toSnailfishNumber(),
|
||||
reduce("[[[[[7,7],[7,7]],[[8,7],[8,7]]],[[[7,0],[7,7]],9]],[[[[4,2],2],6],[8,7]]]".toSnailfishNumber())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun explode() {
|
||||
assertEquals(
|
||||
"[[[[0,9],2],3],4]".toSnailfishNumber(),
|
||||
"[[[[[9,8],1],2],3],4]".toSnailfishNumber().explode(mutableListOf()).second
|
||||
)
|
||||
assertEquals(
|
||||
"[7,[6,[5,[7,0]]]]".toSnailfishNumber(),
|
||||
"[7,[6,[5,[4,[3,2]]]]]".toSnailfishNumber().explode(mutableListOf()).second
|
||||
)
|
||||
assertEquals(
|
||||
"[[6,[5,[7,0]]],3]".toSnailfishNumber(),
|
||||
"[[6,[5,[4,[3,2]]]],1]".toSnailfishNumber().explode(mutableListOf()).second
|
||||
)
|
||||
assertEquals(
|
||||
"[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]".toSnailfishNumber(),
|
||||
"[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]".toSnailfishNumber().explode(mutableListOf()).second
|
||||
)
|
||||
assertEquals(
|
||||
"[[3,[2,[8,0]]],[9,[5,[7,0]]]]".toSnailfishNumber(),
|
||||
"[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]".toSnailfishNumber().explode(mutableListOf()).second
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun split() {
|
||||
assertEquals(asSFNumber(5, 5), asSFNumber(10).split(mutableListOf()).second)
|
||||
assertEquals(asSFNumber(5, 6), asSFNumber(11).split(mutableListOf()).second)
|
||||
assertEquals(asSFNumber(6, 6), asSFNumber(12).split(mutableListOf()).second)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getType() {
|
||||
assertEquals(SnailfishNumberType.Regular, "13".toSnailfishNumber().type)
|
||||
assertEquals(SnailfishNumberType.Pair, "[1,2]".toSnailfishNumber().type)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMagnitude() {
|
||||
assertEquals(143, "[[1,2],[[3,4],5]]".toSnailfishNumber().magnitude)
|
||||
assertEquals(1384, "[[[[0,7],4],[[7,8],[6,0]]],[8,1]]".toSnailfishNumber().magnitude)
|
||||
assertEquals(445, "[[[[1,1],[2,2]],[3,3]],[4,4]]".toSnailfishNumber().magnitude)
|
||||
assertEquals(791, "[[[[3,0],[5,3]],[4,4]],[5,5]]".toSnailfishNumber().magnitude)
|
||||
assertEquals(1137, "[[[[5,0],[7,4]],[5,5]],[6,6]]".toSnailfishNumber().magnitude)
|
||||
assertEquals(3488, "[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]".toSnailfishNumber().magnitude)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun plus() {
|
||||
assertEquals(
|
||||
"[[[[0,7],4],[[7,8],[6,0]]],[8,1]]".toSnailfishNumber(),
|
||||
"[[[[4,3],4],4],[7,[[8,4],9]]]".toSnailfishNumber() + "[1,1]".toSnailfishNumber()
|
||||
)
|
||||
assertEquals(
|
||||
"[[[[4,0],[5,4]],[[7,7],[6,0]]],[[8,[7,7]],[[7,9],[5,0]]]]".toSnailfishNumber(),
|
||||
"[[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]]".toSnailfishNumber() + "[7,[[[3,7],[4,3]],[[6,3],[8,8]]]]".toSnailfishNumber()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun plusFold() {
|
||||
assertEquals(
|
||||
"[[[[1,1],[2,2]],[3,3]],[4,4]]".toSnailfishNumber(), listOf(
|
||||
"[1,1]", "[2,2]", "[3,3]", "[4,4]"
|
||||
).map(String::toSnailfishNumber).reduce(SpecialNumber::plus)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"[[[[3,0],[5,3]],[4,4]],[5,5]]".toSnailfishNumber(), listOf(
|
||||
"[1,1]", "[2,2]", "[3,3]", "[4,4]", "[5,5]",
|
||||
).map(String::toSnailfishNumber).reduce(SpecialNumber::plus)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"[[[[5,0],[7,4]],[5,5]],[6,6]]".toSnailfishNumber(), listOf(
|
||||
"[1,1]", "[2,2]", "[3,3]", "[4,4]", "[5,5]", "[6,6]",
|
||||
).map(String::toSnailfishNumber).reduce(SpecialNumber::plus)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun plusFoldLarger() {
|
||||
val numbers = listOf(
|
||||
"[[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]]",
|
||||
"[7,[[[3,7],[4,3]],[[6,3],[8,8]]]]",
|
||||
"[[2,[[0,8],[3,4]]],[[[6,7],1],[7,[1,6]]]]",
|
||||
"[[[[2,4],7],[6,[0,5]]],[[[6,8],[2,8]],[[2,1],[4,5]]]]",
|
||||
"[7,[5,[[3,8],[1,4]]]]",
|
||||
"[[2,[2,2]],[8,[8,1]]]",
|
||||
"[2,9]",
|
||||
"[1,[[[9,3],9],[[9,0],[0,7]]]]",
|
||||
"[[[5,[7,4]],7],1]",
|
||||
"[[[[4,2],2],6],[8,7]]",
|
||||
).map(String::toSnailfishNumber)
|
||||
|
||||
val expected = listOf(
|
||||
"[[[[4,0],[5,4]],[[7,7],[6,0]]],[[8,[7,7]],[[7,9],[5,0]]]]",
|
||||
"[[[[6,7],[6,7]],[[7,7],[0,7]]],[[[8,7],[7,7]],[[8,8],[8,0]]]]",
|
||||
"[[[[7,0],[7,7]],[[7,7],[7,8]]],[[[7,7],[8,8]],[[7,7],[8,7]]]]",
|
||||
"[[[[7,7],[7,8]],[[9,5],[8,7]]],[[[6,8],[0,8]],[[9,9],[9,0]]]]",
|
||||
"[[[[6,6],[6,6]],[[6,0],[6,7]]],[[[7,7],[8,9]],[8,[8,1]]]]",
|
||||
"[[[[6,6],[7,7]],[[0,7],[7,7]]],[[[5,5],[5,6]],9]]",
|
||||
"[[[[7,8],[6,7]],[[6,8],[0,8]]],[[[7,7],[5,0]],[[5,5],[5,6]]]]",
|
||||
"[[[[7,7],[7,7]],[[8,7],[8,7]]],[[[7,0],[7,7]],9]]",
|
||||
"[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]"
|
||||
).map(String::toSnailfishNumber)
|
||||
|
||||
assertEquals(
|
||||
expected.last(), numbers.reduce(SpecialNumber::plus)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun plusFoldSample() {
|
||||
val numbers = listOf(
|
||||
"[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]",
|
||||
"[[[5,[2,8]],4],[5,[[9,9],0]]]",
|
||||
"[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]",
|
||||
"[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]",
|
||||
"[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]",
|
||||
"[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]",
|
||||
"[[[[5,4],[7,7]],8],[[8,3],8]]",
|
||||
"[[9,3],[[9,9],[6,[4,9]]]]",
|
||||
"[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]",
|
||||
"[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]"
|
||||
).map(String::toSnailfishNumber)
|
||||
|
||||
assertEquals(
|
||||
"[[[[6,6],[7,6]],[[7,7],[7,0]]],[[[7,7],[7,7]],[[7,8],[9,9]]]]".toSnailfishNumber(),
|
||||
numbers.reduce(SpecialNumber::plus)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deepExplode() {
|
||||
println(reduce("[[[[9,[3,[2,[3,0]]]],[[7,9],5]],8],9]".toSnailfishNumber()))
|
||||
}
|
||||
}
|
10
src/year2021/day18/sample.txt
Normal file
10
src/year2021/day18/sample.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]
|
||||
[[[5,[2,8]],4],[5,[[9,9],0]]]
|
||||
[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]
|
||||
[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]
|
||||
[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]
|
||||
[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]
|
||||
[[[[5,4],[7,7]],8],[[8,3],8]]
|
||||
[[9,3],[[9,9],[6,[4,9]]]]
|
||||
[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]
|
||||
[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]
|
Loading…
Reference in a new issue