1
0
Fork 0

day(18): add solution

Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
Matej Focko 2021-12-18 23:39:14 +01:00
parent 90e1bfc107
commit 3701f6ffdf
3 changed files with 414 additions and 0 deletions

212
src/year2021/day18/Day18.kt Normal file
View 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))
}

View 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()))
}
}

View 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]]]