Skip to content

Day 13 complete. #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/main/kotlin/common/intpos2d/intpos2d.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ package common.intpos2d

typealias IntPos2D = Pair<Int, Int>

val Zero2D = IntPos2D(0, 0)

fun IntPos2D.isZero() =
this == Zero2D

fun IntPos2D.isNotZero() =
!isZero()

operator fun IntPos2D.plus(other: IntPos2D) =
IntPos2D(this.first + other.first, this.second + other.second)

Expand All @@ -20,6 +28,21 @@ operator fun Int.times(pos: IntPos2D): IntPos2D =
operator fun IntPos2D.times(factor: Int) =
factor * this

operator fun IntPos2D.times(other: IntPos2D): IntPos2D =
IntPos2D(first * other.first, second * other.second)

operator fun IntPos2D.rem(modulus: Int): IntPos2D =
IntPos2D(this.first % modulus, this.second % modulus)

operator fun IntPos2D.div(denominator: Int): IntPos2D =
IntPos2D(this.first / denominator, this.second / denominator)

infix fun IntPos2D.dot(other: IntPos2D): Int =
first * other.first + second * other.second

fun IntPos2D.x(): Int = first
fun IntPos2D.y(): Int = second

enum class Direction(val delta: IntPos2D) {
NORTH(IntPos2D(-1, 0)),
EAST(IntPos2D(0, 1)),
Expand Down Expand Up @@ -48,10 +71,6 @@ enum class Direction(val delta: IntPos2D) {
}
}

//fun IntPos2D.neighbours(rows: Int, cols: Int): List<IntPos2D> =
// Direction.entries.map { this + it.delta }
// .filter { it.first in 0 until rows && it.second in 0 until rows }

val Diagonals: Set<Pair<Direction, Direction>> = setOf(
Pair(Direction.NORTH, Direction.WEST),
Pair(Direction.WEST, Direction.SOUTH),
Expand Down
74 changes: 74 additions & 0 deletions src/main/kotlin/day13/day13.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Advent of Code 2024, Day 13.
// By Sebastian Raaphorst, 2024.

package day13

import common.aocreader.fetchAdventOfCodeInput
import common.intpos2d.*
import common.runner.timedFunction
import java.math.BigInteger

private typealias BigIntPos = Pair<BigInteger, BigInteger>

data class Machine(val deltaA: IntPos2D, val deltaB: IntPos2D, val prizePos: IntPos2D) {
/**
* Let a be the number of times we hit button A, and b be the number of times we hit button B.
* We want an integer solution to the equation:
* [dAx dBx] (a) = (px)
* [dAy dBy] (b) (py)
*/
fun calculateSolution(adjustment: BigIntPos): BigIntPos? {
val determinant = deltaA.x() * deltaB.y() - deltaB.x() * deltaA.y()
if (determinant == 0) return null
val detBigInteger = determinant.toBigInteger()

val adjPx = prizePos.x().toBigInteger() + adjustment.first
val adjPy = prizePos.y().toBigInteger() + adjustment.second
val abDet = BigIntPos(
deltaB.y().toBigInteger() * adjPx - deltaB.x().toBigInteger() * adjPy,
deltaA.x().toBigInteger() * adjPy - deltaA.y().toBigInteger() * adjPx)

val detX = abDet.first % detBigInteger
val detY = abDet.second % detBigInteger

return if (detX == BigInteger.ZERO && detY == BigInteger.ZERO)
BigIntPos(abDet.first / detBigInteger, abDet.second / detBigInteger)
else null
}

companion object {
val TokenCostA = 3.toBigInteger()
val TokenCostB = BigInteger.ONE

private val MachineRegex = """[XY][+=](\d+)""".toRegex()

fun parse(input: String): Machine {
val matches = MachineRegex.findAll(input).map { it.groupValues[1].toInt() }.toList()
val (deltaA, deltaB, prizePos) = matches.chunked(2).map { IntPos2D(it[0], it[1]) }
return Machine(deltaA, deltaB, prizePos)
}
}
}

fun parse(input: String): List<Machine> =
input.split("""[\r\n]{2}""".toRegex()).map { Machine.parse(it.trim()) }

fun answer(input: String, adjustment: BigIntPos = BigIntPos(BigInteger.ZERO, BigInteger.ZERO)): BigInteger =
parse(input)
.mapNotNull { it.calculateSolution(adjustment) }
.sumOf { sol -> sol.first * Machine.TokenCostA + sol.second * Machine.TokenCostB }

fun answer1(input: String): BigInteger =
answer(input)

private val adjustment2 = BigInteger("10000000000000")

fun answer2(input: String): BigInteger =
answer(input, BigIntPos(adjustment2, adjustment2))

fun main() {
val input = fetchAdventOfCodeInput(2024, 13)
println("--- Day 13: Claw Contraption ---")
timedFunction("Part 1") { answer1(input) } // 28262
timedFunction("Part 2") { answer2(input) } // 101406661266314
}
2 changes: 1 addition & 1 deletion src/test/kotlin/day12/day12.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Advent of Code 2024, Day 12
// Advent of Code 2024, Day 12.
// By Sebastian Raaphorst, 2024.

package day12
Expand Down
35 changes: 35 additions & 0 deletions src/test/kotlin/day13/day13.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Advent of Code 2024, Day 13.
// By Sebastian Raaphorst, 2024.

package day13

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class Day13 {
companion object {
val input1 =
"""
Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400

Button A: X+26, Y+66
Button B: X+67, Y+21
Prize: X=12748, Y=12176

Button A: X+17, Y+86
Button B: X+84, Y+37
Prize: X=7870, Y=6450

Button A: X+69, Y+23
Button B: X+27, Y+71
Prize: X=18641, Y=10279
""".trimIndent()
}

@Test
fun `Problem 1 example`() {
assertEquals(480.toBigInteger(), answer1(input1))
}
}
Loading