Compare commits
108
Commits
3c4b32fc78
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cda0dccc65 | ||
|
|
5e031c27cc | ||
|
|
de3b0795e3 | ||
|
|
2190f9e1ac | ||
|
|
0192b33570 | ||
|
|
a69be406b1 | ||
|
|
7495f9e378 | ||
|
|
ccc3fdad4c | ||
|
|
bd591263f6 | ||
|
|
4982f2cb3c | ||
|
|
6299b10b85 | ||
|
|
caf82dd7a9 | ||
|
|
d2044f2d6a | ||
|
|
9a2a22805b | ||
|
|
909fb5c7d2 | ||
|
|
52af74ecd5 | ||
|
|
7a58627132 | ||
|
|
5fc0e8756a | ||
|
|
2b3549c84c | ||
|
|
128ffbb10f | ||
|
|
f51f87736b | ||
|
|
c82509468a | ||
|
|
732c69df0e | ||
|
|
1f323bcc82 | ||
|
|
17c6a9baac | ||
|
|
ab5fd24260 | ||
|
|
f399fcdea7 | ||
|
|
9a0b4af4ac | ||
|
|
79360efdf4 | ||
|
|
d49d3614c7 | ||
|
|
1a303d9632 | ||
|
|
e7d733846f | ||
|
|
e00e002cd6 | ||
|
|
0d5809398c | ||
|
|
277bc6cc8e | ||
|
|
e222975b2a | ||
|
|
343414cd04 | ||
|
|
bc542bd29c | ||
|
|
0cb180ddf3 | ||
|
|
f494b763cf | ||
|
|
aa0f1dad74 | ||
|
|
9047a97185 | ||
|
|
1b2332a49e | ||
|
|
b0e4461a7d | ||
|
|
76c01e01e5 | ||
|
|
3ee70151f1 | ||
|
|
0592f4a862 | ||
|
|
0d6bf1ca43 | ||
|
|
9e7ac0057b | ||
|
|
90cf5a8335 | ||
|
|
9a8b316122 | ||
|
|
778b38b3a4 | ||
|
|
34c0c213c4 | ||
|
|
3d17ed29c7 | ||
|
|
bc1888299b | ||
|
|
4b84ce2803 | ||
|
|
abb0225d06 | ||
|
|
89b897b2c1 | ||
|
|
26661cbfd8 | ||
|
|
8cdfdda1f4 | ||
|
|
0876e5c92c | ||
|
|
9d61ce1bc4 | ||
|
|
15ce1ad06e | ||
|
|
8d492486fb | ||
|
|
ea9f44a12c | ||
|
|
d2487ed762 | ||
|
|
3cc10ea7c3 | ||
|
|
a14958b8a6 | ||
|
|
782d12c048 | ||
|
|
4bf79c0e89 | ||
|
|
77618b3534 | ||
|
|
64ba775ca6 | ||
|
|
b770db22c6 | ||
|
|
7695a005c9 | ||
|
|
79797d8ea2 | ||
|
|
8f879f456e | ||
|
|
6b27f3741d | ||
|
|
73164a0ee3 | ||
|
|
0acad0121c | ||
|
|
6b904bb42c | ||
|
|
12d2e88da1 | ||
|
|
bfee59402e | ||
|
|
946048f3f1 | ||
|
|
50a8574a10 | ||
|
|
8f1e9e238e | ||
|
|
1eca157417 | ||
|
|
db7f45670e | ||
|
|
4d1948a068 | ||
|
|
158ed23e9e | ||
|
|
04dc4d6ba9 | ||
|
|
8115b1c8ae | ||
|
|
1f9f6a69ea | ||
|
|
e35a97b62d | ||
|
|
095bbd4bc7 | ||
|
|
3a8a2d79a9 | ||
|
|
6eb93c51a3 | ||
|
|
d1d022f208 | ||
|
|
3586f49f91 | ||
|
|
b3f648a01e | ||
|
|
226e61a203 | ||
|
|
3b1410e2fd | ||
|
|
9ececeeb43 | ||
|
|
ee3629989b | ||
|
|
834d170921 | ||
|
|
b98653f2b9 | ||
|
|
56fcbfe165 | ||
|
|
fb3a5ce5d2 | ||
|
|
837e5827f4 |
@@ -1,4 +1,4 @@
|
||||
# advent-of-code-2023
|
||||
# Advent of Code
|
||||
|
||||
Welcome to the Advent of Code[^aoc] Kotlin project created by [chrisly42][gitea] using the [Advent of Code Kotlin Template][template] delivered by JetBrains.
|
||||
|
||||
@@ -6,7 +6,7 @@ In this repository, chrisly42 is about to provide solutions for the puzzles usin
|
||||
|
||||
This repo also contains a puzzle input downloader and generates coding templates for each day.
|
||||
|
||||
As it is my first time I'm taking part in the AoC, I've started doing some last year's puzzles as a warmup and to test the template.
|
||||
As 2023 was my first time taking part in the AoC, I've started doing some earlier year's puzzles as a warmup and to test the template.
|
||||
|
||||
[^aoc]:
|
||||
[Advent of Code][aoc] – An annual event of Christmas-oriented programming challenges started December 2015.
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.9.20"
|
||||
kotlin("jvm") version "2.1.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -8,7 +8,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.kittinunf.fuel:fuel:3.0.0-alpha1")
|
||||
implementation("com.mohamedrejeb.ksoup:ksoup-html:0.2.1")
|
||||
implementation("com.mohamedrejeb.ksoup:ksoup-html:0.6.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
@@ -19,6 +19,6 @@ tasks {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = "8.4"
|
||||
gradleVersion = "8.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
${description}
|
||||
${weblink}
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+102
-2
@@ -1,4 +1,5 @@
|
||||
import java.lang.Long.numberOfTrailingZeros
|
||||
import java.math.BigInteger
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
@@ -47,7 +48,7 @@ fun primeFactors(n: Long, bits: BitSet): List<Long> {
|
||||
if (remainder == 1L) {
|
||||
return trivialFactors
|
||||
}
|
||||
val factors = ArrayList<Long>(trivialFactors)
|
||||
val factors = ArrayList(trivialFactors)
|
||||
if (!remainder.toBigInteger().isProbablePrime(20)) {
|
||||
val divisor = rhoBrent(remainder)
|
||||
factors.addAll(primeFactors(divisor, bits))
|
||||
@@ -187,7 +188,106 @@ fun gcdPositive(aIn: Long, bIn: Long): Long {
|
||||
return a shl shift
|
||||
}
|
||||
|
||||
fun calcPrimeFactorsAndPhi(n: Long, primes: MutableList<Long>, allPrimes: MutableSet<Long>): Pair<List<Pair<Long, Int>>, Long> {
|
||||
// ax + by = gcdExtendedPositive(a, b)
|
||||
fun extendedGcd(a: Long, b: Long): Pair<Long, Pair<Long, Long>> {
|
||||
var old_r = a
|
||||
var r = b
|
||||
var old_s = 1L
|
||||
var s = 0L
|
||||
var old_t = 0L
|
||||
var t = 1L
|
||||
while (r != 0L) {
|
||||
val q = old_r / r
|
||||
val rtmp = old_r
|
||||
old_r = r
|
||||
r = rtmp - q * r
|
||||
val stmp = old_s
|
||||
old_s = s
|
||||
s = stmp - q * s
|
||||
val ttmp = old_t
|
||||
old_t = t
|
||||
t = ttmp - q * t
|
||||
}
|
||||
|
||||
return old_r to (old_s to old_t)
|
||||
}
|
||||
|
||||
fun extendedGcd(a: BigInteger, b: BigInteger): Pair<BigInteger, Pair<BigInteger, BigInteger>> {
|
||||
var old_r = a
|
||||
var r = b
|
||||
var old_s = BigInteger.ONE
|
||||
var s = BigInteger.ZERO
|
||||
var old_t = BigInteger.ZERO
|
||||
var t = BigInteger.ONE
|
||||
while (r != BigInteger.ZERO) {
|
||||
val q = old_r / r
|
||||
val rtmp = old_r
|
||||
old_r = r
|
||||
r = rtmp - q * r
|
||||
val stmp = old_s
|
||||
old_s = s
|
||||
s = stmp - q * s
|
||||
val ttmp = old_t
|
||||
old_t = t
|
||||
t = ttmp - q * t
|
||||
}
|
||||
|
||||
return old_r to (old_s to old_t)
|
||||
}
|
||||
|
||||
fun extendedGcd(v: List<Long>): Pair<Long, List<Long>> {
|
||||
if (v.size < 2) throw IllegalArgumentException("Expected at least 2 elements")
|
||||
|
||||
val gcds = ArrayList<Long>(v.size)
|
||||
val coeffs = ArrayList<Long>(v.size)
|
||||
var (gcd, p1) = extendedGcd(v[0], v[1])
|
||||
coeffs.add(p1.first)
|
||||
coeffs.add(p1.second)
|
||||
gcds.add(gcd)
|
||||
gcds.add(gcd)
|
||||
for (i in 2 until v.size) {
|
||||
val (gcdnew, pi) = extendedGcd(gcd, v[i])
|
||||
gcd = gcdnew
|
||||
coeffs.add(pi.second)
|
||||
gcds.add(gcd)
|
||||
}
|
||||
for (i in gcds.indices) {
|
||||
if (gcds[i] != gcd) {
|
||||
coeffs[i] *= gcds[i] / gcd
|
||||
}
|
||||
}
|
||||
return gcd to coeffs
|
||||
}
|
||||
|
||||
fun extendedGcdBigInteger(v: List<BigInteger>): Pair<BigInteger, List<BigInteger>> {
|
||||
if (v.size < 2) throw IllegalArgumentException("Expected at least 2 elements")
|
||||
|
||||
val gcds = ArrayList<BigInteger>(v.size)
|
||||
val coeffs = ArrayList<BigInteger>(v.size)
|
||||
var (gcd, p1) = extendedGcd(v[0], v[1])
|
||||
coeffs.add(p1.first)
|
||||
coeffs.add(p1.second)
|
||||
gcds.add(gcd)
|
||||
gcds.add(gcd)
|
||||
for (i in 2 until v.size) {
|
||||
val (gcdnew, pi) = extendedGcd(gcd, v[i])
|
||||
gcd = gcdnew
|
||||
coeffs.add(pi.second)
|
||||
gcds.add(gcd)
|
||||
}
|
||||
for (i in gcds.indices) {
|
||||
if (gcds[i] != gcd) {
|
||||
coeffs[i] *= gcds[i] / gcd
|
||||
}
|
||||
}
|
||||
return gcd to coeffs
|
||||
}
|
||||
|
||||
fun calcPrimeFactorsAndPhi(
|
||||
n: Long,
|
||||
primes: MutableList<Long>,
|
||||
allPrimes: MutableSet<Long>
|
||||
): Pair<List<Pair<Long, Int>>, Long> {
|
||||
val factors = ArrayList<Pair<Long, Int>>()
|
||||
var phi = 1L
|
||||
var rem = n
|
||||
|
||||
+131
-30
@@ -2,19 +2,33 @@ import java.io.File
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
|
||||
fun Int.gcd(b: Int): Int = if (b == 0) this else b.gcd(this % b)
|
||||
fun Long.gcd(b: Long): Long = if (b == 0L) this else b.gcd(this % b)
|
||||
tailrec fun Int.gcd(b: Int): Int = if (b == 0) this else b.gcd(this % b)
|
||||
tailrec fun Long.gcd(b: Long): Long = if (b == 0L) this else b.gcd(this % b)
|
||||
fun Int.lcm(b: Int): Int = this / gcd(b) * b
|
||||
fun Long.lcm(b: Long): Long = this / gcd(b) * b
|
||||
fun Iterable<Int>.lcm(): Int = reduce(Int::lcm)
|
||||
fun Iterable<Long>.lcm(): Long = reduce(Long::lcm)
|
||||
|
||||
fun Iterable<Pair<Long, Long>>.chineseRemainder(): Long {
|
||||
val prod = map { it.second.toBigInteger() }.reduce(BigInteger::times)
|
||||
var sum = BigInteger.ZERO
|
||||
for ((rem, coprime) in this) {
|
||||
val cobig = coprime.toBigInteger()
|
||||
val p = prod / cobig
|
||||
sum += rem.toBigInteger() * p.modInverse(cobig) * p
|
||||
}
|
||||
return (sum % prod).toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads lines from the given input txt file.
|
||||
*/
|
||||
fun readInput(name: String) = File("src", "$name.txt")
|
||||
.readLines()
|
||||
|
||||
fun String.splitInts(vararg del: String = arrayOf(" ")): List<Int> = split(*del).filter(String::isNotBlank).map(String::toInt)
|
||||
fun String.splitLongs(vararg del: String = arrayOf(" ")): List<Long> = split(*del).filter(String::isNotBlank).map(String::toLong)
|
||||
|
||||
fun listOfIntegerLists(input: List<String>): ArrayList<List<Int>> {
|
||||
val output = ArrayList<List<Int>>()
|
||||
var currList = ArrayList<Int>()
|
||||
@@ -34,6 +48,7 @@ fun listOfIntegerLists(input: List<String>): ArrayList<List<Int>> {
|
||||
|
||||
data class RelPos(val dc: Int, val dr: Int) {
|
||||
fun translate(c: Int, r: Int) = RelPos(c + dc, r + dr)
|
||||
fun translate(rp: RelPos) = RelPos(rp.dc + dc, rp.dr + dr)
|
||||
}
|
||||
|
||||
class CharGrid {
|
||||
@@ -60,6 +75,13 @@ class CharGrid {
|
||||
val data: Array<CharArray>
|
||||
val bChar: Char
|
||||
|
||||
constructor(width: Int, height: Int, borderChar: Char = ' ', fillChar: Char = borderChar) {
|
||||
bChar = borderChar
|
||||
this.width = width
|
||||
this.height = height
|
||||
data = Array(height) { CharArray(width) { fillChar } }
|
||||
}
|
||||
|
||||
constructor(input: List<String>, borderChar: Char = ' ') {
|
||||
bChar = borderChar
|
||||
width = input[0].length
|
||||
@@ -75,23 +97,35 @@ class CharGrid {
|
||||
}
|
||||
|
||||
operator fun get(col: Int, row: Int) = getOrNull(col, row) ?: bChar
|
||||
|
||||
operator fun set(col: Int, row: Int, newChar: Char?) {
|
||||
if (newChar != null && isInside(col, row)) data[row][col] = newChar
|
||||
}
|
||||
|
||||
operator fun get(pos: RelPos) = getOrNull(pos) ?: bChar
|
||||
operator fun set(pos: RelPos, newChar: Char?) {
|
||||
if (newChar != null && isInside(pos)) data[pos.dr][pos.dc] = newChar
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("Use RelPos version instead")
|
||||
fun setOrThrow(col: Int, row: Int, newChar: Char) {
|
||||
if (!isInside(col, row)) throw IndexOutOfBoundsException("$col, $row out of bounds")
|
||||
set(col, row, newChar)
|
||||
}
|
||||
|
||||
@Deprecated("Use RelPos version instead")
|
||||
fun getOrNull(col: Int, row: Int) = if (isInside(col, row)) data[row][col] else null
|
||||
fun getOrNull(pos: RelPos) = if (isInside(pos)) data[pos.dr][pos.dc] else null
|
||||
|
||||
@Deprecated("Use RelPos version instead")
|
||||
fun isInside(col: Int, row: Int) = (col in 0 until width) && (row in 0 until height)
|
||||
fun isInside(pos: RelPos) = (pos.dc in 0 until width) && (pos.dr in 0 until height)
|
||||
|
||||
fun getHorizNumberValueAt(rp: RelPos) = getHorizNumberValueAt(rp.dc, rp.dr)
|
||||
|
||||
fun getHorizNumberValueAt(col: Int, row: Int): Pair<Int, List<RelPos>> {
|
||||
val relPos = getHorizNumberRelPosAt(col, row)
|
||||
return relPos.map { this[col + it.dc, row + it.dr].digitToInt() }.reduce { acc, digit -> acc * 10 + digit } to relPos
|
||||
fun getHorizNumberValueAt(pos: RelPos): Pair<Int, List<RelPos>> {
|
||||
val relPos = getHorizNumberRelPosAt(pos.dc, pos.dr)
|
||||
return relPos.map { this[pos.translate(it)].digitToInt() }
|
||||
.reduce { acc, digit -> acc * 10 + digit } to relPos
|
||||
}
|
||||
|
||||
fun getHorizNumberRelPosAt(col: Int, row: Int): List<RelPos> {
|
||||
@@ -107,54 +141,121 @@ class CharGrid {
|
||||
|
||||
fun copyOf() = CharGrid(Array(height) { data[it].copyOf() }, borderChar = bChar)
|
||||
|
||||
fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) {
|
||||
for (r in 0 until height) {
|
||||
for (c in 0 until width) {
|
||||
this[c, r] = op(this, c, r)
|
||||
}
|
||||
}
|
||||
fun generateGridPos() = generateSequence(RelPos(0, 0)) {
|
||||
if (it.dc + 1 < width) RelPos(it.dc + 1, it.dr) else if (it.dr + 1 < height) RelPos(0, it.dr + 1) else null
|
||||
}
|
||||
|
||||
fun apply(op: (content: Char) -> Char?) {
|
||||
applyWithPos { grid: CharGrid, col, row -> op(grid[col, row]) }
|
||||
}
|
||||
@Deprecated("Use RelPos version instead")
|
||||
fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) =
|
||||
generateGridPos().forEach { this[it] = op(this, it.dc, it.dr) }
|
||||
|
||||
fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) =
|
||||
generateGridPos().forEach { this[it] = op(this, it) }
|
||||
|
||||
fun applyWithPos(relposes: Iterable<RelPos>, op: (grid: CharGrid, pos: RelPos) -> Char?) =
|
||||
relposes.forEach { this[it] = op(this, it) }
|
||||
|
||||
fun apply(op: (content: Char) -> Char?) =
|
||||
applyWithPos { grid: CharGrid, pos -> op(grid[pos]) }
|
||||
|
||||
fun grow(
|
||||
relposes: Iterable<RelPos>, predicate: (content: Char) -> Boolean,
|
||||
op: (orgContent: Char, oldContent: Char, col: Int, row: Int) -> Char? = { orgContent: Char, _: Char, _: Int, _: Int -> orgContent }
|
||||
op: (orgContent: Char, oldContent: Char, pos: RelPos) -> Char? = { orgContent: Char, _: Char, _: RelPos -> orgContent }
|
||||
): CharGrid {
|
||||
val newGrid = copyOf()
|
||||
for (r in 0 until height) {
|
||||
for (c in 0 until width) {
|
||||
if (predicate(get(c, r))) {
|
||||
relposes
|
||||
.map { it.translate(c, r) }
|
||||
.filter { isInside(it.dc, it.dr) }
|
||||
.forEach { newGrid[it.dc, it.dr] = op(this[c, r], newGrid[it.dc, it.dr], it.dc, it.dr) }
|
||||
}
|
||||
generateGridPos().filter { predicate(get(it)) }
|
||||
.forEach { pos ->
|
||||
relposes
|
||||
.map { it.translate(pos) }
|
||||
.filter { isInside(it) }
|
||||
.forEach { newGrid[it] = op(this[pos], newGrid[it], it) }
|
||||
}
|
||||
}
|
||||
return newGrid
|
||||
}
|
||||
|
||||
fun collect(c: Int, r: Int, relposes: Iterable<RelPos>, skipBorder: Boolean = true): List<Char> =
|
||||
fun collectRelative(c: Int, r: Int, relposes: Iterable<RelPos>, skipBorder: Boolean = true): List<Char> =
|
||||
collectRelative(RelPos(c, r), relposes, skipBorder)
|
||||
|
||||
fun collectRelative(pos: RelPos, relposes: Iterable<RelPos>, skipBorder: Boolean = true): List<Char> =
|
||||
if (skipBorder) {
|
||||
relposes.mapNotNull { getOrNull(c + it.dc, r + it.dr) }
|
||||
relposes.mapNotNull { getOrNull(pos.translate(it)) }
|
||||
} else {
|
||||
relposes.map { get(c + it.dc, r + it.dr) }
|
||||
relposes.map { get(pos.translate(it)) }
|
||||
}
|
||||
|
||||
fun countStrings(relposeList: Iterable<Iterable<RelPos>>, string: String) =
|
||||
findMatches(relposeList) { it == string }.count()
|
||||
|
||||
fun findMatches(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: String) -> Boolean): List<Pair<String, RelPos>> {
|
||||
return generateGridPos().flatMap { pos ->
|
||||
relposeList.mapNotNull { rp ->
|
||||
val st = collectRelative(pos, rp).joinToString("")
|
||||
if (predicate(st)) st to pos else null
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
|
||||
fun findCombinedMatches(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: List<String>) -> Boolean): List<RelPos> {
|
||||
return generateGridPos().filter { pos ->
|
||||
predicate(relposeList.map { rp -> collectRelative(pos, rp).joinToString("") })
|
||||
}.toList()
|
||||
}
|
||||
|
||||
fun matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
|
||||
relposes.filter { predicate(get(c + it.dc, r + it.dr)) }
|
||||
|
||||
fun matchRelative(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
|
||||
relposes.filter { predicate(get(pos.translate(it))) }
|
||||
|
||||
fun matchAbsoluteRelPos(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
|
||||
relposes.map { pos.translate(it) }.filter { predicate(get(it)) }
|
||||
|
||||
fun marchMatching(pos: RelPos, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
|
||||
relposes.map { pos.translate(it) }.filter { predicate(get(it)) }
|
||||
|
||||
fun findMatches(predicate: (char: Char) -> Boolean): List<Pair<Int, Int>> {
|
||||
return generateGridPos().filter { predicate(get(it)) }.map { it.dc to it.dr }.toList()
|
||||
}
|
||||
|
||||
fun findMatchesRelPos(predicate: (char: Char) -> Boolean): List<RelPos> {
|
||||
return generateGridPos().filter { predicate(get(it)) }.toList()
|
||||
}
|
||||
|
||||
fun collectMatches(predicate: (char: Char) -> Boolean): List<Pair<Char, RelPos>> {
|
||||
return generateGridPos().filter { predicate(get(it)) }.map { get(it) to it }.toList()
|
||||
}
|
||||
|
||||
fun addBorder(borderChar: Char = bChar): CharGrid {
|
||||
val topBottom = CharArray(width + 2) { borderChar }
|
||||
return CharGrid(Array(height + 2) {
|
||||
if (it == 0 || it == height + 1) topBottom else
|
||||
CharArray(width + 2) { r -> if (r == 0 || r == width + 1) borderChar else get(it - 1, r - 1) }
|
||||
CharArray(width + 2) { r -> if (r == 0 || r == width + 1) borderChar else get(r - 1, it - 1) }
|
||||
}, borderChar)
|
||||
}
|
||||
|
||||
fun debug() {
|
||||
data.joinToString("\n") { it.concatToString() }.println()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as CharGrid
|
||||
|
||||
if (width != other.width) return false
|
||||
if (height != other.height) return false
|
||||
if (!data.contentDeepEquals(other.data)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = width
|
||||
result = 31 * result + height
|
||||
result = 31 * result + data.contentDeepHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package aoc2015
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 1: Not Quite Lisp ---
|
||||
https://adventofcode.com/2015/day/1
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
(()(()(
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
return input.single().count { it == '(' } - input.single().count { it == ')' }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
var floor = 0
|
||||
for ((i, c) in input.single().withIndex()) {
|
||||
if (c == '(') floor++ else floor--
|
||||
if (floor < 0) return i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2015/Day01_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 3)
|
||||
check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2015/Day01")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package aoc2015
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 2: I Was Told There Would Be No Math ---
|
||||
https://adventofcode.com/2015/day/2
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
2x3x4
|
||||
1x1x10
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
return input.sumOf {
|
||||
val dims = it.split("x").map(String::toInt).sorted()
|
||||
2 * dims[0] * dims[1] + 2 * dims[1] * dims[2] + 2 * dims[0] * dims[2] + dims[0] * dims[1]
|
||||
}
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
return input.sumOf {
|
||||
val dims = it.split("x").map(String::toInt).sorted()
|
||||
2 * (dims[0] + dims[1]) + dims.reduce(Int::times)
|
||||
}
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2015/Day02_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 58 + 43)
|
||||
check(testInputPart2Result == 34 + 14)
|
||||
|
||||
val input = readInput("aoc2015/Day02")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package aoc2015
|
||||
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 3: Perfectly Spherical Houses in a Vacuum ---
|
||||
https://adventofcode.com/2015/day/3
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
^>v<
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val houses = HashSet<RelPos>()
|
||||
var x = 0
|
||||
var y = 0
|
||||
for (c in input.single()) {
|
||||
when (c) {
|
||||
'<' -> x--
|
||||
'>' -> x++
|
||||
'^' -> y--
|
||||
'v' -> y++
|
||||
}
|
||||
houses.add(RelPos(x, y))
|
||||
}
|
||||
return houses.size
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val houses = HashSet<RelPos>()
|
||||
var xs = 0
|
||||
var ys = 0
|
||||
var xr = 0
|
||||
var yr = 0
|
||||
var parity = false
|
||||
for (c in input.single()) {
|
||||
if (parity) {
|
||||
when (c) {
|
||||
'<' -> xr--
|
||||
'>' -> xr++
|
||||
'^' -> yr--
|
||||
'v' -> yr++
|
||||
}
|
||||
houses.add(RelPos(xr, yr))
|
||||
} else {
|
||||
when (c) {
|
||||
'<' -> xs--
|
||||
'>' -> xs++
|
||||
'^' -> ys--
|
||||
'v' -> ys++
|
||||
}
|
||||
houses.add(RelPos(xs, ys))
|
||||
}
|
||||
parity = !parity
|
||||
}
|
||||
return houses.size
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2015/Day03_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 4)
|
||||
check(testInputPart2Result == 3)
|
||||
|
||||
val input = readInput("aoc2015/Day03")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package aoc2015
|
||||
|
||||
import md5
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 4: The Ideal Stocking Stuffer ---
|
||||
https://adventofcode.com/2015/day/4
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
abcdef
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
return IntRange(0, 10000000).first { (input.single() + it.toString()).md5().startsWith("00000") }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
return IntRange(0, 10000000).first { (input.single() + it.toString()).md5().startsWith("000000") }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2015/Day04_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 609043)
|
||||
//check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2015/Day04")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package aoc2015
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 5: Doesn't He Have Intern-Elves For This? ---
|
||||
https://adventofcode.com/2015/day/5
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
ugknbfddgicrmopn
|
||||
aaa
|
||||
jchzalrnumimnmhp
|
||||
haegwjzuvuyypxyu
|
||||
dvszwmarrgswjxmb
|
||||
"""
|
||||
|
||||
val inlineTestInput2 = """
|
||||
qjhvhtzxzqqjkmpb
|
||||
xxyxx
|
||||
uurcxstgmygtbstg
|
||||
ieodomkazucvgmuy
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
var nice = 0
|
||||
for (i in input) {
|
||||
val vowels = i.filter { "aeiou".contains(it) }.take(3).count() == 3
|
||||
val double = i.zipWithNext { a, b -> a == b }.any { it }
|
||||
val sane = listOf("ab", "cd", "pq", "xy").none { i.contains(it) }
|
||||
if (vowels && double && sane) nice++
|
||||
}
|
||||
return nice
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
var nice = 0
|
||||
for (i in input) {
|
||||
val double = i.zipWithNext { a, b -> "$a$b" }.any { i.replace(it, "").length < i.length - 3 }
|
||||
val spaced = i.zipWithNext().zipWithNext().any { it.first.first == it.second.second }
|
||||
if (double && spaced) nice++
|
||||
}
|
||||
return nice
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2015/Day05_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput2)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 2)
|
||||
check(testInputPart2Result == 2)
|
||||
|
||||
val input = readInput("aoc2015/Day05")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package aoc2015
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 6: Probably a Fire Hazard ---
|
||||
https://adventofcode.com/2015/day/6
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(1000, 1000)
|
||||
input.forEach {
|
||||
val (op, x1, y1, x2, y2) = "(toggle|turn off|turn on) (\\d+),(\\d+) through (\\d+),(\\d+)".toRegex().matchEntire(it)!!.destructured
|
||||
for (y in y1.toInt()..y2.toInt()) {
|
||||
for (x in x1.toInt()..x2.toInt()) {
|
||||
grid[x, y] = when (op) {
|
||||
"turn on" -> '*'
|
||||
"turn off" -> ' '
|
||||
"toggle" -> if (grid[x, y] == '*') ' ' else '*'
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return grid.generateGridPos().count { grid[it] == '*' }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = IntArray(1000 * 1000)
|
||||
input.forEach {
|
||||
val (op, x1, y1, x2, y2) = "(toggle|turn off|turn on) (\\d+),(\\d+) through (\\d+),(\\d+)".toRegex().matchEntire(it)!!.destructured
|
||||
for (y in y1.toInt()..y2.toInt()) {
|
||||
for (x in x1.toInt()..x2.toInt()) {
|
||||
grid[x + y * 1000] = (grid[x + y * 1000] + when (op) {
|
||||
"turn on" -> 1
|
||||
"turn off" -> -1
|
||||
"toggle" -> 2
|
||||
else -> throw IllegalStateException()
|
||||
}).coerceAtLeast(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return grid.sum()
|
||||
}
|
||||
|
||||
val input = readInput("aoc2015/Day06")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
+1
-37
@@ -6,43 +6,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 1: Calorie Counting ---
|
||||
Santa's reindeer typically eat regular reindeer food, but they need a lot of magical energy to deliver presents on Christmas. For that, their favorite snack is a special type of star fruit that only grows deep in the jungle. The Elves have brought you on their annual expedition to the grove where the fruit grows.
|
||||
To supply enough magical energy, the expedition needs to retrieve a minimum of fifty stars by December 25th. Although the Elves assure you that the grove has plenty of fruit, you decide to grab any fruit you see along the way, just in case.
|
||||
Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck!
|
||||
The jungle must be too overgrown and difficult to navigate in vehicles or access from the air; the Elves' expedition traditionally goes on foot. As your boats approach land, the Elves begin taking inventory of their supplies. One important consideration is food - in particular, the number of Calories each Elf is carrying (your puzzle input).
|
||||
The Elves take turns writing down the number of Calories contained by the various meals, snacks, rations, etc. that they've brought with them, one item per line. Each Elf separates their own inventory from the previous Elf's inventory (if any) by a blank line.
|
||||
For example, suppose the Elves finish writing their items' Calories and end up with the following list:
|
||||
1000
|
||||
2000
|
||||
3000
|
||||
|
||||
4000
|
||||
|
||||
5000
|
||||
6000
|
||||
|
||||
7000
|
||||
8000
|
||||
9000
|
||||
|
||||
10000
|
||||
|
||||
This list represents the Calories of the food carried by five Elves:
|
||||
|
||||
The first Elf is carrying food with 1000, 2000, and 3000 Calories, a total of 6000 Calories.
|
||||
The second Elf is carrying one food item with 4000 Calories.
|
||||
The third Elf is carrying food with 5000 and 6000 Calories, a total of 11000 Calories.
|
||||
The fourth Elf is carrying food with 7000, 8000, and 9000 Calories, a total of 24000 Calories.
|
||||
The fifth Elf is carrying one food item with 10000 Calories.
|
||||
|
||||
In case the Elves get hungry and need extra snacks, they need to know which Elf to ask: they'd like to know how many Calories are being carried by the Elf carrying the most Calories. In the example above, this is 24000 (carried by the fourth Elf).
|
||||
Find the Elf carrying the most Calories. How many total Calories is that Elf carrying?
|
||||
--- Part Two ---
|
||||
By the time you calculate the answer to the Elves' question, they've already realized that the Elf carrying the most Calories of food might eventually run out of snacks.
|
||||
To avoid this unacceptable situation, the Elves would instead like to know the total Calories carried by the top three Elves carrying the most Calories. That way, even if one of those Elves runs out of snacks, they still have two backups.
|
||||
In the example above, the top three Elves are the fourth Elf (with 24000 Calories), then the third Elf (with 11000 Calories), then the fifth Elf (with 10000 Calories). The sum of the Calories carried by these three elves is 45000.
|
||||
Find the top three Elves carrying the most Calories. How many Calories are those Elves carrying in total?
|
||||
|
||||
https://adventofcode.com/2022/day/1
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+1
-30
@@ -5,36 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 2: Rock Paper Scissors ---
|
||||
The Elves begin to set up camp on the beach. To decide whose tent gets to be closest to the snack storage, a giant Rock Paper Scissors tournament is already in progress.
|
||||
Rock Paper Scissors is a game between two players. Each game contains many rounds; in each round, the players each simultaneously choose one of Rock, Paper, or Scissors using a hand shape. Then, a winner for that round is selected: Rock defeats Scissors, Scissors defeats Paper, and Paper defeats Rock. If both players choose the same shape, the round instead ends in a draw.
|
||||
Appreciative of your help yesterday, one Elf gives you an encrypted strategy guide (your puzzle input) that they say will be sure to help you win. "The first column is what your opponent is going to play: A for Rock, B for Paper, and C for Scissors. The second column--" Suddenly, the Elf is called away to help with someone's tent.
|
||||
The second column, you reason, must be what you should play in response: X for Rock, Y for Paper, and Z for Scissors. Winning every time would be suspicious, so the responses must have been carefully chosen.
|
||||
The winner of the whole tournament is the player with the highest score. Your total score is the sum of your scores for each round. The score for a single round is the score for the shape you selected (1 for Rock, 2 for Paper, and 3 for Scissors) plus the score for the outcome of the round (0 if you lost, 3 if the round was a draw, and 6 if you won).
|
||||
Since you can't be sure if the Elf is trying to help you or trick you, you should calculate the score you would get if you were to follow the strategy guide.
|
||||
For example, suppose you were given the following strategy guide:
|
||||
A Y
|
||||
B X
|
||||
C Z
|
||||
|
||||
This strategy guide predicts and recommends the following:
|
||||
|
||||
In the first round, your opponent will choose Rock (A), and you should choose Paper (Y). This ends in a win for you with a score of 8 (2 because you chose Paper + 6 because you won).
|
||||
In the second round, your opponent will choose Paper (B), and you should choose Rock (X). This ends in a loss for you with a score of 1 (1 + 0).
|
||||
The third round is a draw with both players choosing Scissors, giving you a score of 3 + 3 = 6.
|
||||
|
||||
In this example, if you were to follow the strategy guide, you would get a total score of 15 (8 + 1 + 6).
|
||||
What would your total score be if everything goes exactly according to your strategy guide?
|
||||
--- Part Two ---
|
||||
The Elf finishes helping with the tent and sneaks back over to you. "Anyway, the second column says how the round needs to end: X means you need to lose, Y means you need to end the round in a draw, and Z means you need to win. Good luck!"
|
||||
The total score is still calculated in the same way, but now you need to figure out what shape to choose so the round ends as indicated. The example above now goes like this:
|
||||
|
||||
In the first round, your opponent will choose Rock (A), and you need the round to end in a draw (Y), so you also choose Rock. This gives you a score of 1 + 3 = 4.
|
||||
In the second round, your opponent will choose Paper (B), and you choose Rock so you lose (X) with a score of 1 + 0 = 1.
|
||||
In the third round, you will defeat your opponent's Scissors with Rock for a score of 1 + 6 = 7.
|
||||
|
||||
Now that you're correctly decrypting the ultra top secret strategy guide, you would get a total score of 12.
|
||||
Following the Elf's instructions for the second column, what would your total score be if everything goes exactly according to your strategy guide?
|
||||
|
||||
https://adventofcode.com/2022/day/2
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+1
-46
@@ -5,52 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 3: Rucksack Reorganization ---
|
||||
One Elf has the important job of loading all of the rucksacks with supplies for the jungle journey. Unfortunately, that Elf didn't quite follow the packing instructions, and so a few items now need to be rearranged.
|
||||
Each rucksack has two large compartments. All items of a given type are meant to go into exactly one of the two compartments. The Elf that did the packing failed to follow this rule for exactly one item type per rucksack.
|
||||
The Elves have made a list of all of the items currently in each rucksack (your puzzle input), but they need your help finding the errors. Every item type is identified by a single lowercase or uppercase letter (that is, a and A refer to different types of items).
|
||||
The list of items for each rucksack is given as characters all on a single line. A given rucksack always has the same number of items in each of its two compartments, so the first half of the characters represent items in the first compartment, while the second half of the characters represent items in the second compartment.
|
||||
For example, suppose you have the following list of contents from six rucksacks:
|
||||
vJrwpWtwJgWrhcsFMMfFFhFp
|
||||
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
|
||||
PmmdzqPrVvPwwTWBwg
|
||||
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
|
||||
ttgJtRGJQctTZtZT
|
||||
CrZsJsPPZsGzwwsLwLmpwMDw
|
||||
|
||||
|
||||
The first rucksack contains the items vJrwpWtwJgWrhcsFMMfFFhFp, which means its first compartment contains the items vJrwpWtwJgWr, while the second compartment contains the items hcsFMMfFFhFp. The only item type that appears in both compartments is lowercase p.
|
||||
The second rucksack's compartments contain jqHRNqRjqzjGDLGL and rsFMfFZSrLrFZsSL. The only item type that appears in both compartments is uppercase L.
|
||||
The third rucksack's compartments contain PmmdzqPrV and vPwwTWBwg; the only common item type is uppercase P.
|
||||
The fourth rucksack's compartments only share item type v.
|
||||
The fifth rucksack's compartments only share item type t.
|
||||
The sixth rucksack's compartments only share item type s.
|
||||
|
||||
To help prioritize item rearrangement, every item type can be converted to a priority:
|
||||
|
||||
Lowercase item types a through z have priorities 1 through 26.
|
||||
Uppercase item types A through Z have priorities 27 through 52.
|
||||
|
||||
In the above example, the priority of the item type that appears in both compartments of each rucksack is 16 (p), 38 (L), 42 (P), 22 (v), 20 (t), and 19 (s); the sum of these is 157.
|
||||
Find the item type that appears in both compartments of each rucksack. What is the sum of the priorities of those item types?
|
||||
--- Part Two ---
|
||||
As you finish identifying the misplaced items, the Elves come to you with another issue.
|
||||
For safety, the Elves are divided into groups of three. Every Elf carries a badge that identifies their group. For efficiency, within each group of three Elves, the badge is the only item type carried by all three Elves. That is, if a group's badge is item type B, then all three Elves will have item type B somewhere in their rucksack, and at most two of the Elves will be carrying any other item type.
|
||||
The problem is that someone forgot to put this year's updated authenticity sticker on the badges. All of the badges need to be pulled out of the rucksacks so the new authenticity stickers can be attached.
|
||||
Additionally, nobody wrote down which item type corresponds to each group's badges. The only way to tell which item type is the right one is by finding the one item type that is common between all three Elves in each group.
|
||||
Every set of three lines in your list corresponds to a single group, but each group can have a different badge item type. So, in the above example, the first group's rucksacks are the first three lines:
|
||||
vJrwpWtwJgWrhcsFMMfFFhFp
|
||||
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
|
||||
PmmdzqPrVvPwwTWBwg
|
||||
|
||||
And the second group's rucksacks are the next three lines:
|
||||
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
|
||||
ttgJtRGJQctTZtZT
|
||||
CrZsJsPPZsGzwwsLwLmpwMDw
|
||||
|
||||
In the first group, the only item type that appears in all three rucksacks is lowercase r; this must be their badges. In the second group, their badge item type must be Z.
|
||||
Priorities for these items must still be found to organize the sticker attachment efforts: here, they are 18 (r) for the first group and 52 (Z) for the second group. The sum of these is 70.
|
||||
Find the item type that corresponds to the badges of each three-Elf group. What is the sum of the priorities of those item types?
|
||||
|
||||
https://adventofcode.com/2022/day/3
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+1
-49
@@ -5,55 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 4: Camp Cleanup ---
|
||||
Space needs to be cleared before the last supplies can be unloaded from the ships, and so several Elves have been assigned the job of cleaning up sections of the camp. Every section has a unique ID number, and each Elf is assigned a range of section IDs.
|
||||
However, as some of the Elves compare their section assignments with each other, they've noticed that many of the assignments overlap. To try to quickly find overlaps and reduce duplicated effort, the Elves pair up and make a big list of the section assignments for each pair (your puzzle input).
|
||||
For example, consider the following list of section assignment pairs:
|
||||
2-4,6-8
|
||||
2-3,4-5
|
||||
5-7,7-9
|
||||
2-8,3-7
|
||||
6-6,4-6
|
||||
2-6,4-8
|
||||
|
||||
For the first few pairs, this list means:
|
||||
|
||||
Within the first pair of Elves, the first Elf was assigned sections 2-4 (sections 2, 3, and 4), while the second Elf was assigned sections 6-8 (sections 6, 7, 8).
|
||||
The Elves in the second pair were each assigned two sections.
|
||||
The Elves in the third pair were each assigned three sections: one got sections 5, 6, and 7, while the other also got 7, plus 8 and 9.
|
||||
|
||||
This example list uses single-digit section IDs to make it easier to draw; your actual list might contain larger numbers. Visually, these pairs of section assignments look like this:
|
||||
.234..... 2-4
|
||||
.....678. 6-8
|
||||
|
||||
.23...... 2-3
|
||||
...45.... 4-5
|
||||
|
||||
....567.. 5-7
|
||||
......789 7-9
|
||||
|
||||
.2345678. 2-8
|
||||
..34567.. 3-7
|
||||
|
||||
.....6... 6-6
|
||||
...456... 4-6
|
||||
|
||||
.23456... 2-6
|
||||
...45678. 4-8
|
||||
|
||||
Some of the pairs have noticed that one of their assignments fully contains the other. For example, 2-8 fully contains 3-7, and 6-6 is fully contained by 4-6. In pairs where one assignment fully contains the other, one Elf in the pair would be exclusively cleaning sections their partner will already be cleaning, so these seem like the most in need of reconsideration. In this example, there are 2 such pairs.
|
||||
In how many assignment pairs does one range fully contain the other?
|
||||
--- Part Two ---
|
||||
It seems like there is still quite a bit of duplicate work planned. Instead, the Elves would like to know the number of pairs that overlap at all.
|
||||
In the above example, the first two pairs (2-4,6-8 and 2-3,4-5) don't overlap, while the remaining four pairs (5-7,7-9, 2-8,3-7, 6-6,4-6, and 2-6,4-8) do overlap:
|
||||
|
||||
5-7,7-9 overlaps in a single section, 7.
|
||||
2-8,3-7 overlaps all of the sections 3 through 7.
|
||||
6-6,4-6 overlaps in a single section, 6.
|
||||
2-6,4-8 overlaps in sections 4, 5, and 6.
|
||||
|
||||
So, in this example, the number of overlapping assignment pairs is 4.
|
||||
In how many assignment pairs do the ranges overlap?
|
||||
|
||||
https://adventofcode.com/2022/day/4
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+4
-86
@@ -2,93 +2,11 @@ package aoc2022
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
|
||||
/*
|
||||
--- Day 5: Supply Stacks ---
|
||||
The expedition can depart as soon as the final supplies have been unloaded from the ships. Supplies are stored in stacks of marked crates, but because the needed supplies are buried under many other crates, the crates need to be rearranged.
|
||||
The ship has a giant cargo crane capable of moving crates between stacks. To ensure none of the crates get crushed or fall over, the crane operator will rearrange them in a series of carefully-planned steps. After the crates are rearranged, the desired crates will be at the top of each stack.
|
||||
The Elves don't want to interrupt the crane operator during this delicate procedure, but they forgot to ask her which crate will end up where, and they want to be ready to unload them as soon as possible so they can embark.
|
||||
They do, however, have a drawing of the starting stacks of crates and the rearrangement procedure (your puzzle input). For example:
|
||||
[D]
|
||||
[N] [C]
|
||||
[Z] [M] [P]
|
||||
1 2 3
|
||||
|
||||
move 1 from 2 to 1
|
||||
move 3 from 1 to 3
|
||||
move 2 from 2 to 1
|
||||
move 1 from 1 to 2
|
||||
|
||||
In this example, there are three stacks of crates. Stack 1 contains two crates: crate Z is on the bottom, and crate N is on top. Stack 2 contains three crates; from bottom to top, they are crates M, C, and D. Finally, stack 3 contains a single crate, P.
|
||||
Then, the rearrangement procedure is given. In each step of the procedure, a quantity of crates is moved from one stack to a different stack. In the first step of the above rearrangement procedure, one crate is moved from stack 2 to stack 1, resulting in this configuration:
|
||||
[D]
|
||||
[N] [C]
|
||||
[Z] [M] [P]
|
||||
1 2 3
|
||||
|
||||
In the second step, three crates are moved from stack 1 to stack 3. Crates are moved one at a time, so the first crate to be moved (D) ends up below the second and third crates:
|
||||
[Z]
|
||||
[N]
|
||||
[C] [D]
|
||||
[M] [P]
|
||||
1 2 3
|
||||
|
||||
Then, both crates are moved from stack 2 to stack 1. Again, because crates are moved one at a time, crate C ends up below crate M:
|
||||
[Z]
|
||||
[N]
|
||||
[M] [D]
|
||||
[C] [P]
|
||||
1 2 3
|
||||
|
||||
Finally, one crate is moved from stack 1 to stack 2:
|
||||
[Z]
|
||||
[N]
|
||||
[D]
|
||||
[C] [M] [P]
|
||||
1 2 3
|
||||
|
||||
The Elves just need to know which crate will end up on top of each stack; in this example, the top crates are C in stack 1, M in stack 2, and Z in stack 3, so you should combine these together and give the Elves the message CMZ.
|
||||
After the rearrangement procedure completes, what crate ends up on top of each stack?
|
||||
--- Part Two ---
|
||||
As you watch the crane operator expertly rearrange the crates, you notice the process isn't following your prediction.
|
||||
Some mud was covering the writing on the side of the crane, and you quickly wipe it away. The crane isn't a CrateMover 9000 - it's a CrateMover 9001.
|
||||
The CrateMover 9001 is notable for many new and exciting features: air conditioning, leather seats, an extra cup holder, and the ability to pick up and move multiple crates at once.
|
||||
Again considering the example above, the crates begin in the same configuration:
|
||||
[D]
|
||||
[N] [C]
|
||||
[Z] [M] [P]
|
||||
1 2 3
|
||||
|
||||
Moving a single crate from stack 2 to stack 1 behaves the same as before:
|
||||
[D]
|
||||
[N] [C]
|
||||
[Z] [M] [P]
|
||||
1 2 3
|
||||
|
||||
However, the action of moving three crates from stack 1 to stack 3 means that those three moved crates stay in the same order, resulting in this new configuration:
|
||||
[D]
|
||||
[N]
|
||||
[C] [Z]
|
||||
[M] [P]
|
||||
1 2 3
|
||||
|
||||
Next, as both crates are moved from stack 2 to stack 1, they retain their order as well:
|
||||
[D]
|
||||
[N]
|
||||
[C] [Z]
|
||||
[M] [P]
|
||||
1 2 3
|
||||
|
||||
Finally, a single crate is still moved from stack 1 to stack 2, but now it's crate C that gets moved:
|
||||
[D]
|
||||
[N]
|
||||
[Z]
|
||||
[M] [C] [P]
|
||||
1 2 3
|
||||
|
||||
In this example, the CrateMover 9001 has put the crates in a totally different order: MCD.
|
||||
Before the rearrangement process finishes, update your simulation so that the Elves know where they should stand to be ready to unload the final supplies. After the rearrangement procedure completes, what crate ends up on top of each stack?
|
||||
|
||||
https://adventofcode.com/2022/day/5
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
@@ -106,7 +24,7 @@ move 1 from 1 to 2
|
||||
data class StackOp(val num: Int, val src: Int, val dest: Int)
|
||||
|
||||
fun part1(input: List<String>): String {
|
||||
val numItems = input.first { it.startsWith(" 1") }.split(" ").filter { it.isNotBlank() }.map { it.toInt() }.max()
|
||||
val numItems = input.first { it.startsWith(" 1") }.splitInts().max()
|
||||
val stackOps = input.filter { it.startsWith("move") }.map {
|
||||
val line = it.split(" ")
|
||||
StackOp(line[1].toInt(), line[3].toInt() - 1, line[5].toInt() - 1)
|
||||
@@ -131,7 +49,7 @@ move 1 from 1 to 2
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): String {
|
||||
val numItems = input.first { it.startsWith(" 1") }.split(" ").filter { it.isNotBlank() }.map { it.toInt() }.max()
|
||||
val numItems = input.first { it.startsWith(" 1") }.splitInts().max()
|
||||
val stackOps = input.filter { it.startsWith("move") }.map {
|
||||
val line = it.split(" ")
|
||||
StackOp(line[1].toInt(), line[3].toInt() - 1, line[5].toInt() - 1)
|
||||
|
||||
+1
-34
@@ -5,40 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 6: Tuning Trouble ---
|
||||
The preparations are finally complete; you and the Elves leave camp on foot and begin to make your way toward the star fruit grove.
|
||||
As you move through the dense undergrowth, one of the Elves gives you a handheld device. He says that it has many fancy features, but the most important one to set up right now is the communication system.
|
||||
However, because he's heard you have significant experience dealing with signal-based systems, he convinced the other Elves that it would be okay to give you their one malfunctioning device - surely you'll have no problem fixing it.
|
||||
As if inspired by comedic timing, the device emits a few colorful sparks.
|
||||
To be able to communicate with the Elves, the device needs to lock on to their signal. The signal is a series of seemingly-random characters that the device receives one at a time.
|
||||
To fix the communication system, you need to add a subroutine to the device that detects a start-of-packet marker in the datastream. In the protocol being used by the Elves, the start of a packet is indicated by a sequence of four characters that are all different.
|
||||
The device will send your subroutine a datastream buffer (your puzzle input); your subroutine needs to identify the first position where the four most recently received characters were all different. Specifically, it needs to report the number of characters from the beginning of the buffer to the end of the first such four-character marker.
|
||||
For example, suppose you receive the following datastream buffer:
|
||||
mjqjpqmgbljsphdztnvjfqwrcgsmlb
|
||||
After the first three characters (mjq) have been received, there haven't been enough characters received yet to find the marker. The first time a marker could occur is after the fourth character is received, making the most recent four characters mjqj. Because j is repeated, this isn't a marker.
|
||||
The first time a marker appears is after the seventh character arrives. Once it does, the last four characters received are jpqm, which are all different. In this case, your subroutine should report the value 7, because the first start-of-packet marker is complete after 7 characters have been processed.
|
||||
Here are a few more examples:
|
||||
|
||||
bvwbjplbgvbhsrlpgdmjqwftvncz: first marker after character 5
|
||||
nppdvjthqldpwncqszvftbrmjlhg: first marker after character 6
|
||||
nznrnfrfntjfmvfwmzdfjlvtqnbhcprsg: first marker after character 10
|
||||
zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw: first marker after character 11
|
||||
|
||||
How many characters need to be processed before the first start-of-packet marker is detected?
|
||||
|
||||
--- Part Two ---
|
||||
Your device's communication system is correctly detecting packets, but still isn't working. It looks like it also needs to look for messages.
|
||||
|
||||
A start-of-message marker is just like a start-of-packet marker, except it consists of 14 distinct characters rather than 4.
|
||||
|
||||
Here are the first positions of start-of-message markers for all of the above examples:
|
||||
|
||||
mjqjpqmgbljsphdztnvjfqwrcgsmlb: first marker after character 19
|
||||
bvwbjplbgvbhsrlpgdmjqwftvncz: first marker after character 23
|
||||
nppdvjthqldpwncqszvftbrmjlhg: first marker after character 23
|
||||
nznrnfrfntjfmvfwmzdfjlvtqnbhcprsg: first marker after character 29
|
||||
zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw: first marker after character 26
|
||||
How many characters need to be processed before the first start-of-message marker is detected?
|
||||
|
||||
https://adventofcode.com/2022/day/6
|
||||
*/
|
||||
fun main() {
|
||||
val inlineTestInput = """
|
||||
|
||||
+1
-92
@@ -5,98 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 7: No Space Left On Device ---
|
||||
You can hear birds chirping and raindrops hitting leaves as the expedition proceeds. Occasionally, you can even hear much louder sounds in the distance; how big do the animals get out here, anyway?
|
||||
The device the Elves gave you has problems with more than just its communication system. You try to run a system update:
|
||||
$ system-update --please --pretty-please-with-sugar-on-top
|
||||
Error: No space left on device
|
||||
|
||||
Perhaps you can delete some files to make space for the update?
|
||||
You browse around the filesystem to assess the situation and save the resulting terminal output (your puzzle input). For example:
|
||||
$ cd /
|
||||
$ ls
|
||||
dir a
|
||||
14848514 b.txt
|
||||
8504156 c.dat
|
||||
dir d
|
||||
$ cd a
|
||||
$ ls
|
||||
dir e
|
||||
29116 f
|
||||
2557 g
|
||||
62596 h.lst
|
||||
$ cd e
|
||||
$ ls
|
||||
584 i
|
||||
$ cd ..
|
||||
$ cd ..
|
||||
$ cd d
|
||||
$ ls
|
||||
4060174 j
|
||||
8033020 d.log
|
||||
5626152 d.ext
|
||||
7214296 k
|
||||
|
||||
The filesystem consists of a tree of files (plain data) and directories (which can contain other directories or files). The outermost directory is called /. You can navigate around the filesystem, moving into or out of directories and listing the contents of the directory you're currently in.
|
||||
Within the terminal output, lines that begin with $ are commands you executed, very much like some modern computers:
|
||||
|
||||
cd means change directory. This changes which directory is the current directory, but the specific result depends on the argument:
|
||||
|
||||
cd x moves in one level: it looks in the current directory for the directory named x and makes it the current directory.
|
||||
cd .. moves out one level: it finds the directory that contains the current directory, then makes that directory the current directory.
|
||||
cd / switches the current directory to the outermost directory, /.
|
||||
|
||||
|
||||
ls means list. It prints out all of the files and directories immediately contained by the current directory:
|
||||
|
||||
123 abc means that the current directory contains a file named abc with size 123.
|
||||
dir xyz means that the current directory contains a directory named xyz.
|
||||
|
||||
|
||||
|
||||
Given the commands and output in the example above, you can determine that the filesystem looks visually like this:
|
||||
- / (dir)
|
||||
- a (dir)
|
||||
- e (dir)
|
||||
- i (file, size=584)
|
||||
- f (file, size=29116)
|
||||
- g (file, size=2557)
|
||||
- h.lst (file, size=62596)
|
||||
- b.txt (file, size=14848514)
|
||||
- c.dat (file, size=8504156)
|
||||
- d (dir)
|
||||
- j (file, size=4060174)
|
||||
- d.log (file, size=8033020)
|
||||
- d.ext (file, size=5626152)
|
||||
- k (file, size=7214296)
|
||||
|
||||
Here, there are four directories: / (the outermost directory), a and d (which are in /), and e (which is in a). These directories also contain files of various sizes.
|
||||
Since the disk is full, your first step should probably be to find directories that are good candidates for deletion. To do this, you need to determine the total size of each directory. The total size of a directory is the sum of the sizes of the files it contains, directly or indirectly. (Directories themselves do not count as having any intrinsic size.)
|
||||
The total sizes of the directories above can be found as follows:
|
||||
|
||||
The total size of directory e is 584 because it contains a single file i of size 584 and no other directories.
|
||||
The directory a has total size 94853 because it contains files f (size 29116), g (size 2557), and h.lst (size 62596), plus file i indirectly (a contains e which contains i).
|
||||
Directory d has total size 24933642.
|
||||
As the outermost directory, / contains every file. Its total size is 48381165, the sum of the size of every file.
|
||||
|
||||
To begin, find all of the directories with a total size of at most 100000, then calculate the sum of their total sizes. In the example above, these directories are a and e; the sum of their total sizes is 95437 (94853 + 584). (As in this example, this process can count files more than once!)
|
||||
Find all of the directories with a total size of at most 100000. What is the sum of the total sizes of those directories?
|
||||
|
||||
--- Part Two ---
|
||||
Now, you're ready to choose a directory to delete.
|
||||
|
||||
The total disk space available to the filesystem is 70000000. To run the update, you need unused space of at least 30000000. You need to find a directory you can delete that will free up enough space to run the update.
|
||||
|
||||
In the example above, the total size of the outermost directory (and thus the total amount of used space) is 48381165; this means that the size of the unused space must currently be 21618835, which isn't quite the 30000000 required by the update. Therefore, the update still requires a directory with total size of at least 8381165 to be deleted before it can run.
|
||||
|
||||
To achieve this, you have the following options:
|
||||
|
||||
Delete directory e, which would increase unused space by 584.
|
||||
Delete directory a, which would increase unused space by 94853.
|
||||
Delete directory d, which would increase unused space by 24933642.
|
||||
Delete directory /, which would increase unused space by 48381165.
|
||||
Directories e and a are both too small; deleting them would not free up enough space. However, directories d and / are both big enough! Between these, choose the smallest: d, increasing unused space by 24933642.
|
||||
|
||||
Find the smallest directory that, if deleted, would free up enough space on the filesystem to run the update. What is the total size of that directory?
|
||||
https://adventofcode.com/2022/day/7
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+1
-59
@@ -7,65 +7,7 @@ import kotlin.math.min
|
||||
|
||||
/*
|
||||
--- Day 8: Treetop Tree House ---
|
||||
The expedition comes across a peculiar patch of tall trees all planted carefully in a grid. The Elves explain that a previous expedition planted these trees as a reforestation effort. Now, they're curious if this would be a good location for a tree house.
|
||||
First, determine whether there is enough tree cover here to keep a tree house hidden. To do this, you need to count the number of trees that are visible from outside the grid when looking directly along a row or column.
|
||||
The Elves have already launched a quadcopter to generate a map with the height of each tree (your puzzle input). For example:
|
||||
30373
|
||||
25512
|
||||
65332
|
||||
33549
|
||||
35390
|
||||
|
||||
Each tree is represented as a single digit whose value is its height, where 0 is the shortest and 9 is the tallest.
|
||||
A tree is visible if all of the other trees between it and an edge of the grid are shorter than it. Only consider trees in the same row or column; that is, only look up, down, left, or right from any given tree.
|
||||
All of the trees around the edge of the grid are visible - since they are already on the edge, there are no trees to block the view. In this example, that only leaves the interior nine trees to consider:
|
||||
|
||||
The top-left 5 is visible from the left and top. (It isn't visible from the right or bottom since other trees of height 5 are in the way.)
|
||||
The top-middle 5 is visible from the top and right.
|
||||
The top-right 1 is not visible from any direction; for it to be visible, there would need to only be trees of height 0 between it and an edge.
|
||||
The left-middle 5 is visible, but only from the right.
|
||||
The center 3 is not visible from any direction; for it to be visible, there would need to be only trees of at most height 2 between it and an edge.
|
||||
The right-middle 3 is visible from the right.
|
||||
In the bottom row, the middle 5 is visible, but the 3 and 4 are not.
|
||||
|
||||
With 16 trees visible on the edge and another 5 visible in the interior, a total of 21 trees are visible in this arrangement.
|
||||
Consider your map; how many trees are visible from outside the grid?
|
||||
|
||||
--- Part Two ---
|
||||
Content with the amount of tree cover available, the Elves just need to know the best spot to build their tree house: they would like to be able to see a lot of trees.
|
||||
|
||||
To measure the viewing distance from a given tree, look up, down, left, and right from that tree; stop if you reach an edge or at the first tree that is the same height or taller than the tree under consideration. (If a tree is right on the edge, at least one of its viewing distances will be zero.)
|
||||
|
||||
The Elves don't care about distant trees taller than those found by the rules above; the proposed tree house has large eaves to keep it dry, so they wouldn't be able to see higher than the tree house anyway.
|
||||
|
||||
In the example above, consider the middle 5 in the second row:
|
||||
|
||||
30373
|
||||
25512
|
||||
65332
|
||||
33549
|
||||
35390
|
||||
Looking up, its view is not blocked; it can see 1 tree (of height 3).
|
||||
Looking left, its view is blocked immediately; it can see only 1 tree (of height 5, right next to it).
|
||||
Looking right, its view is not blocked; it can see 2 trees.
|
||||
Looking down, its view is blocked eventually; it can see 2 trees (one of height 3, then the tree of height 5 that blocks its view).
|
||||
A tree's scenic score is found by multiplying together its viewing distance in each of the four directions. For this tree, this is 4 (found by multiplying 1 * 1 * 2 * 2).
|
||||
|
||||
However, you can do even better: consider the tree of height 5 in the middle of the fourth row:
|
||||
|
||||
30373
|
||||
25512
|
||||
65332
|
||||
33549
|
||||
35390
|
||||
Looking up, its view is blocked at 2 trees (by another tree with a height of 5).
|
||||
Looking left, its view is not blocked; it can see 2 trees.
|
||||
Looking down, its view is also not blocked; it can see 1 tree.
|
||||
Looking right, its view is blocked at 2 trees (by a massive tree of height 9).
|
||||
This tree's scenic score is 8 (2 * 2 * 1 * 2); this is the ideal spot for the tree house.
|
||||
|
||||
Consider each tree on your map. What is the highest scenic score possible for any tree?
|
||||
|
||||
https://adventofcode.com/2022/day/8
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+1
-33
@@ -5,39 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 1: Trebuchet?! ---
|
||||
Something is wrong with global snow production, and you've been selected to take a look. The Elves have even given you a map; on it, they've used stars to mark the top fifty locations that are likely to be having problems.
|
||||
You've been doing this long enough to know that to restore snow operations, you need to check all fifty stars by December 25th.
|
||||
Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck!
|
||||
You try to ask why they can't just use a weather machine ("not powerful enough") and where they're even sending you ("the sky") and why your map looks mostly blank ("you sure ask a lot of questions") and hang on did you just say the sky ("of course, where do you think snow comes from") when you realize that the Elves are already loading you into a trebuchet ("please hold still, we need to strap you in").
|
||||
As they're making the final adjustments, they discover that their calibration document (your puzzle input) has been amended by a very young Elf who was apparently just excited to show off her art skills. Consequently, the Elves are having trouble reading the values on the document.
|
||||
The newly-improved calibration document consists of lines of text; each line originally contained a specific calibration value that the Elves now need to recover. On each line, the calibration value can be found by combining the first digit and the last digit (in that order) to form a single two-digit number.
|
||||
For example:
|
||||
1abc2
|
||||
pqr3stu8vwx
|
||||
a1b2c3d4e5f
|
||||
treb7uchet
|
||||
|
||||
--- Part Two ---
|
||||
|
||||
In this example, the calibration values of these four lines are 12, 38, 15, and 77. Adding these together produces 142.
|
||||
Consider your entire calibration document. What is the sum of all of the calibration values?
|
||||
|
||||
Your calculation isn't quite right. It looks like some of the digits are actually spelled out with letters: one, two, three, four, five, six, seven, eight, and nine also count as valid "digits".
|
||||
|
||||
Equipped with this new information, you now need to find the real first and last digit on each line. For example:
|
||||
|
||||
two1nine
|
||||
eightwothree
|
||||
abcone2threexyz
|
||||
xtwone3four
|
||||
4nineeightseven2
|
||||
zoneight234
|
||||
7pqrstsixteen
|
||||
|
||||
In this example, the calibration values are 29, 83, 13, 24, 42, 14, and 76. Adding these together produces 281.
|
||||
|
||||
What is the sum of all of the calibration values?
|
||||
|
||||
https://adventofcode.com/2023/day/1
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+1
-37
@@ -6,43 +6,7 @@ import kotlin.math.max
|
||||
|
||||
/*
|
||||
--- Day 2: Cube Conundrum ---
|
||||
You're launched high into the atmosphere! The apex of your trajectory just barely reaches the surface of a large island floating in the sky. You gently land in a fluffy pile of leaves. It's quite cold, but you don't see much snow. An Elf runs over to greet you.
|
||||
The Elf explains that you've arrived at Snow Island and apologizes for the lack of snow. He'll be happy to explain the situation, but it's a bit of a walk, so you have some time. They don't get many visitors up here; would you like to play a game in the meantime?
|
||||
As you walk, the Elf shows you a small bag and some cubes which are either red, green, or blue. Each time you play this game, he will hide a secret number of cubes of each color in the bag, and your goal is to figure out information about the number of cubes.
|
||||
To get information, once a bag has been loaded with cubes, the Elf will reach into the bag, grab a handful of random cubes, show them to you, and then put them back in the bag. He'll do this a few times per game.
|
||||
You play several games and record the information from each game (your puzzle input). Each game is listed with its ID number (like the 11 in Game 11: ...) followed by a semicolon-separated list of subsets of cubes that were revealed from the bag (like 3 red, 5 green, 4 blue).
|
||||
For example, the record of a few games might look like this:
|
||||
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
|
||||
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
|
||||
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
|
||||
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
|
||||
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
|
||||
|
||||
In game 1, three sets of cubes are revealed from the bag (and then put back again). The first set is 3 blue cubes and 4 red cubes; the second set is 1 red cube, 2 green cubes, and 6 blue cubes; the third set is only 2 green cubes.
|
||||
The Elf would first like to know which games would have been possible if the bag contained only 12 red cubes, 13 green cubes, and 14 blue cubes?
|
||||
In the example above, games 1, 2, and 5 would have been possible if the bag had been loaded with that configuration. However, game 3 would have been impossible because at one point the Elf showed you 20 red cubes at once; similarly, game 4 would also have been impossible because the Elf showed you 15 blue cubes at once. If you add up the IDs of the games that would have been possible, you get 8.
|
||||
Determine which games would have been possible if the bag had been loaded with only 12 red cubes, 13 green cubes, and 14 blue cubes. What is the sum of the IDs of those games?
|
||||
|
||||
--- Part Two ---
|
||||
The Elf says they've stopped producing snow because they aren't getting any water! He isn't sure why the water stopped; however, he can show you how to get to the water source to check it out for yourself. It's just up ahead!
|
||||
|
||||
As you continue your walk, the Elf poses a second question: in each game you played, what is the fewest number of cubes of each color that could have been in the bag to make the game possible?
|
||||
|
||||
Again consider the example games from earlier:
|
||||
|
||||
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
|
||||
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
|
||||
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
|
||||
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
|
||||
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
|
||||
In game 1, the game could have been played with as few as 4 red, 2 green, and 6 blue cubes. If any color had even one fewer cube, the game would have been impossible.
|
||||
Game 2 could have been played with a minimum of 1 red, 3 green, and 4 blue cubes.
|
||||
Game 3 must have been played with at least 20 red, 13 green, and 6 blue cubes.
|
||||
Game 4 required at least 14 red, 3 green, and 15 blue cubes.
|
||||
Game 5 needed no fewer than 6 red, 3 green, and 2 blue cubes in the bag.
|
||||
The power of a set of cubes is equal to the numbers of red, green, and blue cubes multiplied together. The power of the minimum set of cubes in game 1 is 48. In games 2-5 it was 12, 1560, 630, and 36, respectively. Adding up these five powers produces the sum 2286.
|
||||
|
||||
For each game, find the minimum set of cubes that must have been present. What is the sum of the power of these sets?
|
||||
https://adventofcode.com/2023/day/2
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+5
-45
@@ -6,47 +6,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 3: Gear Ratios ---
|
||||
You and the Elf eventually reach a gondola lift station; he says the gondola lift will take you up to the water source, but this is as far as he can bring you. You go inside.
|
||||
It doesn't take long to find the gondolas, but there seems to be a problem: they're not moving.
|
||||
"Aaah!"
|
||||
You turn around to see a slightly-greasy Elf with a wrench and a look of surprise. "Sorry, I wasn't expecting anyone! The gondola lift isn't working right now; it'll still be a while before I can fix it." You offer to help.
|
||||
The engineer explains that an engine part seems to be missing from the engine, but nobody can figure out which one. If you can add up all the part numbers in the engine schematic, it should be easy to work out which part is missing.
|
||||
The engine schematic (your puzzle input) consists of a visual representation of the engine. There are lots of numbers and symbols you don't really understand, but apparently any number adjacent to a symbol, even diagonally, is a "part number" and should be included in your sum. (Periods (.) do not count as a symbol.)
|
||||
Here is an example engine schematic:
|
||||
467..114..
|
||||
...*......
|
||||
..35..633.
|
||||
......#...
|
||||
617*......
|
||||
.....+.58.
|
||||
..592.....
|
||||
......755.
|
||||
...$.*....
|
||||
.664.598..
|
||||
|
||||
In this schematic, two numbers are not part numbers because they are not adjacent to a symbol: 114 (top right) and 58 (middle right). Every other number is adjacent to a symbol and so is a part number; their sum is 4361.
|
||||
Of course, the actual engine schematic is much larger. What is the sum of all of the part numbers in the engine schematic?
|
||||
--- Part Two ---
|
||||
The engineer finds the missing part and installs it in the engine! As the engine springs to life, you jump in the closest gondola, finally ready to ascend to the water source.
|
||||
You don't seem to be going very fast, though. Maybe something is still wrong? Fortunately, the gondola has a phone labeled "help", so you pick it up and the engineer answers.
|
||||
Before you can explain the situation, she suggests that you look out the window. There stands the engineer, holding a phone in one hand and waving with the other. You're going so slowly that you haven't even left the station. You exit the gondola.
|
||||
The missing part wasn't the only issue - one of the gears in the engine is wrong. A gear is any * symbol that is adjacent to exactly two part numbers. Its gear ratio is the result of multiplying those two numbers together.
|
||||
This time, you need to find the gear ratio of every gear and add them all up so that the engineer can figure out which gear needs to be replaced.
|
||||
Consider the same engine schematic again:
|
||||
467..114..
|
||||
...*......
|
||||
..35..633.
|
||||
......#...
|
||||
617*......
|
||||
.....+.58.
|
||||
..592.....
|
||||
......755.
|
||||
...$.*....
|
||||
.664.598..
|
||||
|
||||
In this schematic, there are two gears. The first is in the top left; it has part numbers 467 and 35, so its gear ratio is 16345. The second gear is in the lower right; its gear ratio is 451490. (The * adjacent to 617 is not a gear because it is only adjacent to one part number.) Adding up all of the gear ratios produces 467835.
|
||||
What is the sum of all of the gear ratios in your engine schematic?
|
||||
|
||||
https://adventofcode.com/2023/day/3
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
@@ -69,10 +29,10 @@ fun main() {
|
||||
symbolGrid.apply { c -> if (!c.isDigit()) c else '.' }
|
||||
val grownGrid = symbolGrid.grow(CharGrid.BOX_POS, { c -> c != '.' })
|
||||
var sum = 0
|
||||
wholeGrid.applyWithPos { grid, col, row ->
|
||||
if (grid[col, row].isDigit() && grownGrid[col, row] != '.') {
|
||||
val (num, relpos) = grid.getHorizNumberValueAt(col, row)
|
||||
relpos.forEach { grid[col + it.dc, row + it.dr] = '.' }
|
||||
wholeGrid.applyWithPos { grid, pos ->
|
||||
if (grid[pos].isDigit() && grownGrid[pos] != '.') {
|
||||
val (num, relpos) = grid.getHorizNumberValueAt(pos)
|
||||
relpos.forEach { grid[pos.translate(it)] = '.' }
|
||||
sum += num
|
||||
}
|
||||
null
|
||||
|
||||
+6
-40
@@ -2,35 +2,11 @@ package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
|
||||
/*
|
||||
--- Day 4: Scratchcards ---
|
||||
The gondola takes you up. Strangely, though, the ground doesn't seem to be coming with you; you're not climbing a mountain. As the circle of Snow Island recedes below you, an entire new landmass suddenly appears above you! The gondola carries you to the surface of the new island and lurches into the station.
|
||||
As you exit the gondola, the first thing you notice is that the air here is much warmer than it was on Snow Island. It's also quite humid. Is this where the water source is?
|
||||
The next thing you notice is an Elf sitting on the floor across the station in what seems to be a pile of colorful square cards.
|
||||
"Oh! Hello!" The Elf excitedly runs over to you. "How may I be of service?" You ask about water sources.
|
||||
"I'm not sure; I just operate the gondola lift. That does sound like something we'd have, though - this is Island Island, after all! I bet the gardener would know. He's on a different island, though - er, the small kind surrounded by water, not the floating kind. We really need to come up with a better naming scheme. Tell you what: if you can help me with something quick, I'll let you borrow my boat and you can go visit the gardener. I got all these scratchcards as a gift, but I can't figure out what I've won."
|
||||
The Elf leads you over to the pile of colorful cards. There, you discover dozens of scratchcards, all with their opaque covering already scratched off. Picking one up, it looks like each card has two lists of numbers separated by a vertical bar (|): a list of winning numbers and then a list of numbers you have. You organize the information into a table (your puzzle input).
|
||||
As far as the Elf has been able to figure out, you have to figure out which of the numbers you have appear in the list of winning numbers. The first match makes the card worth one point and each match after the first doubles the point value of that card.
|
||||
For example:
|
||||
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
|
||||
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
|
||||
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
|
||||
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
|
||||
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
|
||||
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
|
||||
|
||||
In the above example, card 1 has five winning numbers (41, 48, 83, 86, and 17) and eight numbers you have (83, 86, 6, 31, 17, 9, 48, and 53). Of the numbers you have, four of them (48, 83, 17, and 86) are winning numbers! That means card 1 is worth 8 points (1 for the first match, then doubled three times for each of the three matches after the first).
|
||||
|
||||
Card 2 has two winning numbers (32 and 61), so it is worth 2 points.
|
||||
Card 3 has two winning numbers (1 and 21), so it is worth 2 points.
|
||||
Card 4 has one winning number (84), so it is worth 1 point.
|
||||
Card 5 has no winning numbers, so it is worth no points.
|
||||
Card 6 has no winning numbers, so it is worth no points.
|
||||
|
||||
So, in this example, the Elf's pile of scratchcards is worth 13 points.
|
||||
Take a seat in the large pile of colorful cards. How many points are they worth in total?
|
||||
|
||||
https://adventofcode.com/2023/day/4
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
@@ -48,31 +24,21 @@ Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
|
||||
"Card +(\\d+): (.*)".toRegex().matchEntire(it)?.destructured
|
||||
?.let { (id, game) ->
|
||||
val (wins, numbers) = game.split(" | ")
|
||||
val winSet = wins.split(" ").filter { it.isNotEmpty() }.map { it.toInt() }.toSet()
|
||||
val numberSet = numbers.trim().split(" ").filter { it.isNotEmpty() }.map { it.toInt() }.toSet()
|
||||
val winSet = wins.splitInts().toSet()
|
||||
val numberSet = numbers.splitInts().toSet()
|
||||
val matches = winSet.intersect(numberSet)
|
||||
1 shl (matches.size - 1)
|
||||
}
|
||||
}.sumOf { it!! }
|
||||
}
|
||||
|
||||
fun rec(winTable: List<Int>, pos: Int): Int {
|
||||
var sum = winTable[pos]
|
||||
if (sum > 0) {
|
||||
for (j in pos + 1..(pos + sum).coerceAtMost(winTable.lastIndex)) {
|
||||
sum += rec(winTable, pos)
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val winTable = input.map {
|
||||
"Card +(\\d+): (.*)".toRegex().matchEntire(it)?.destructured
|
||||
?.let { (id, game) ->
|
||||
val (wins, numbers) = game.split(" | ")
|
||||
val winSet = wins.split(" ").filter { it.isNotEmpty() }.map { it.toInt() }.toSet()
|
||||
val numberSet = numbers.trim().split(" ").filter { it.isNotEmpty() }.map { it.toInt() }.toSet()
|
||||
val winSet = wins.splitInts().toSet()
|
||||
val numberSet = numbers.splitInts().toSet()
|
||||
val matches = winSet.intersect(numberSet)
|
||||
matches.size
|
||||
}!!
|
||||
|
||||
+9
-89
@@ -2,92 +2,11 @@ package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitLongs
|
||||
|
||||
/*
|
||||
--- Day 5: If You Give A Seed A Fertilizer ---
|
||||
You take the boat and find the gardener right where you were told he would be: managing a giant "garden" that looks more to you like a farm.
|
||||
"A water source? Island Island is the water source!" You point out that Snow Island isn't receiving any water.
|
||||
"Oh, we had to stop the water because we ran out of sand to filter it with! Can't make snow with dirty water. Don't worry, I'm sure we'll get more sand soon; we only turned off the water a few days... weeks... oh no." His face sinks into a look of horrified realization.
|
||||
"I've been so busy making sure everyone here has food that I completely forgot to check why we stopped getting more sand! There's a ferry leaving soon that is headed over in that direction - it's much faster than your boat. Could you please go check it out?"
|
||||
You barely have time to agree to this request when he brings up another. "While you wait for the ferry, maybe you can help us with our food production problem. The latest Island Island Almanac just arrived and we're having trouble making sense of it."
|
||||
The almanac (your puzzle input) lists all of the seeds that need to be planted. It also lists what type of soil to use with each kind of seed, what type of fertilizer to use with each kind of soil, what type of water to use with each kind of fertilizer, and so on. Every type of seed, soil, fertilizer and so on is identified with a number, but numbers are reused by each category - that is, soil 123 and fertilizer 123 aren't necessarily related to each other.
|
||||
For example:
|
||||
seeds: 79 14 55 13
|
||||
|
||||
seed-to-soil map:
|
||||
50 98 2
|
||||
52 50 48
|
||||
|
||||
soil-to-fertilizer map:
|
||||
0 15 37
|
||||
37 52 2
|
||||
39 0 15
|
||||
|
||||
fertilizer-to-water map:
|
||||
49 53 8
|
||||
0 11 42
|
||||
42 0 7
|
||||
57 7 4
|
||||
|
||||
water-to-light map:
|
||||
88 18 7
|
||||
18 25 70
|
||||
|
||||
light-to-temperature map:
|
||||
45 77 23
|
||||
81 45 19
|
||||
68 64 13
|
||||
|
||||
temperature-to-humidity map:
|
||||
0 69 1
|
||||
1 0 69
|
||||
|
||||
humidity-to-location map:
|
||||
60 56 37
|
||||
56 93 4
|
||||
|
||||
The almanac starts by listing which seeds need to be planted: seeds 79, 14, 55, and 13.
|
||||
The rest of the almanac contains a list of maps which describe how to convert numbers from a source category into numbers in a destination category. That is, the section that starts with seed-to-soil map: describes how to convert a seed number (the source) to a soil number (the destination). This lets the gardener and his team know which soil to use with which seeds, which water to use with which fertilizer, and so on.
|
||||
Rather than list every source number and its corresponding destination number one by one, the maps describe entire ranges of numbers that can be converted. Each line within a map contains three numbers: the destination range start, the source range start, and the range length.
|
||||
Consider again the example seed-to-soil map:
|
||||
50 98 2
|
||||
52 50 48
|
||||
|
||||
The first line has a destination range start of 50, a source range start of 98, and a range length of 2. This line means that the source range starts at 98 and contains two values: 98 and 99. The destination range is the same length, but it starts at 50, so its two values are 50 and 51. With this information, you know that seed number 98 corresponds to soil number 50 and that seed number 99 corresponds to soil number 51.
|
||||
The second line means that the source range starts at 50 and contains 48 values: 50, 51, ..., 96, 97. This corresponds to a destination range starting at 52 and also containing 48 values: 52, 53, ..., 98, 99. So, seed number 53 corresponds to soil number 55.
|
||||
Any source numbers that aren't mapped correspond to the same destination number. So, seed number 10 corresponds to soil number 10.
|
||||
So, the entire list of seed numbers and their corresponding soil numbers looks like this:
|
||||
seed soil
|
||||
0 0
|
||||
1 1
|
||||
... ...
|
||||
48 48
|
||||
49 49
|
||||
50 52
|
||||
51 53
|
||||
... ...
|
||||
96 98
|
||||
97 99
|
||||
98 50
|
||||
99 51
|
||||
|
||||
With this map, you can look up the soil number required for each initial seed number:
|
||||
|
||||
Seed number 79 corresponds to soil number 81.
|
||||
Seed number 14 corresponds to soil number 14.
|
||||
Seed number 55 corresponds to soil number 57.
|
||||
Seed number 13 corresponds to soil number 13.
|
||||
|
||||
The gardener and his team want to get started as soon as possible, so they'd like to know the closest location that needs a seed. Using these maps, find the lowest location number that corresponds to any of the initial seeds. To do this, you'll need to convert each seed number through other categories until you can find its corresponding location number. In this example, the corresponding types are:
|
||||
|
||||
Seed 79, soil 81, fertilizer 81, water 81, light 74, temperature 78, humidity 78, location 82.
|
||||
Seed 14, soil 14, fertilizer 53, water 49, light 42, temperature 42, humidity 43, location 43.
|
||||
Seed 55, soil 57, fertilizer 57, water 53, light 46, temperature 82, humidity 82, location 86.
|
||||
Seed 13, soil 13, fertilizer 52, water 41, light 34, temperature 34, humidity 35, location 35.
|
||||
|
||||
So, the lowest location number in this example is 35.
|
||||
What is the lowest location number that corresponds to any of the initial seed numbers?
|
||||
|
||||
https://adventofcode.com/2023/day/5
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
@@ -147,14 +66,14 @@ humidity-to-location map:
|
||||
if (i.contains(":")) {
|
||||
val (type, data) = i.split(":")
|
||||
when (type) {
|
||||
"seeds" -> seeds = data.trim().split(" ").map { it.toLong() }.toLongArray()
|
||||
"seeds" -> seeds = data.splitLongs().toLongArray()
|
||||
else -> {
|
||||
val from = type.split("-")[0]
|
||||
fromId = typeMap[from]!!
|
||||
}
|
||||
}
|
||||
} else if (i.isNotBlank()) {
|
||||
val (destRangeStart, sourceRangeStart, length) = i.split(" ").map { it.toLong() }
|
||||
val (destRangeStart, sourceRangeStart, length) = i.splitLongs()
|
||||
val rangeLists = mapOfMaps.getOrPut(fromId) { ArrayList() }
|
||||
rangeLists.add(LongRange(sourceRangeStart, sourceRangeStart + length - 1) to destRangeStart)
|
||||
}
|
||||
@@ -192,8 +111,9 @@ humidity-to-location map:
|
||||
if (i.contains(":")) {
|
||||
val (type, data) = i.split(":")
|
||||
when (type) {
|
||||
"seeds" -> seeds = ArrayDeque(data.trim().split(" ").map { it.toLong() }.chunked(2)
|
||||
.map { LongRange(it[0], it[0] + it[1] - 1) })
|
||||
"seeds" -> seeds = ArrayDeque(
|
||||
data.splitLongs().chunked(2)
|
||||
.map { LongRange(it[0], it[0] + it[1] - 1) })
|
||||
|
||||
else -> {
|
||||
val from = type.split("-")[0]
|
||||
@@ -201,7 +121,7 @@ humidity-to-location map:
|
||||
}
|
||||
}
|
||||
} else if (i.isNotBlank()) {
|
||||
val (destRangeStart, sourceRangeStart, length) = i.split(" ").map { it.toLong() }
|
||||
val (destRangeStart, sourceRangeStart, length) = i.splitLongs()
|
||||
val rangeLists = mapOfMaps.getOrPut(fromId) { ArrayList() }
|
||||
rangeLists.add(LongRange(sourceRangeStart, sourceRangeStart + length - 1) to destRangeStart)
|
||||
}
|
||||
@@ -241,7 +161,7 @@ humidity-to-location map:
|
||||
sourceRanges.addFirst(LongRange(newSource, newSource + newSourceLength - 1))
|
||||
found = true
|
||||
break
|
||||
// funnily, this is unused, but it should also be a possibility
|
||||
// funnily, this is unused, but it should also be a possibility
|
||||
// } else if (sourceRange.first < mapRange.first && sourceRange.last > mapRange.last) {
|
||||
// val destLength = mapRange.last + 1 - mapRange.first
|
||||
// val dest = destPos + mapRange.first - sourceRange.first
|
||||
|
||||
+4
-35
@@ -2,42 +2,11 @@ package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
|
||||
/*
|
||||
--- Day 6: Wait For It ---
|
||||
The ferry quickly brings you across Island Island. After asking around, you discover that there is indeed normally a large pile of sand somewhere near here, but you don't see anything besides lots of water and the small island where the ferry has docked.
|
||||
As you try to figure out what to do next, you notice a poster on a wall near the ferry dock. "Boat races! Open to the public! Grand prize is an all-expenses-paid trip to Desert Island!" That must be where the sand comes from! Best of all, the boat races are starting in just a few minutes.
|
||||
You manage to sign up as a competitor in the boat races just in time. The organizer explains that it's not really a traditional race - instead, you will get a fixed amount of time during which your boat has to travel as far as it can, and you win if your boat goes the farthest.
|
||||
As part of signing up, you get a sheet of paper (your puzzle input) that lists the time allowed for each race and also the best distance ever recorded in that race. To guarantee you win the grand prize, you need to make sure you go farther in each race than the current record holder.
|
||||
The organizer brings you over to the area where the boat races are held. The boats are much smaller than you expected - they're actually toy boats, each with a big button on top. Holding down the button charges the boat, and releasing the button allows the boat to move. Boats move faster if their button was held longer, but time spent holding the button counts against the total race time. You can only hold the button at the start of the race, and boats don't move until the button is released.
|
||||
For example:
|
||||
Time: 7 15 30
|
||||
Distance: 9 40 200
|
||||
|
||||
This document describes three races:
|
||||
|
||||
The first race lasts 7 milliseconds. The record distance in this race is 9 millimeters.
|
||||
The second race lasts 15 milliseconds. The record distance in this race is 40 millimeters.
|
||||
The third race lasts 30 milliseconds. The record distance in this race is 200 millimeters.
|
||||
|
||||
Your toy boat has a starting speed of zero millimeters per millisecond. For each whole millisecond you spend at the beginning of the race holding down the button, the boat's speed increases by one millimeter per millisecond.
|
||||
So, because the first race lasts 7 milliseconds, you only have a few options:
|
||||
|
||||
Don't hold the button at all (that is, hold it for 0 milliseconds) at the start of the race. The boat won't move; it will have traveled 0 millimeters by the end of the race.
|
||||
Hold the button for 1 millisecond at the start of the race. Then, the boat will travel at a speed of 1 millimeter per millisecond for 6 milliseconds, reaching a total distance traveled of 6 millimeters.
|
||||
Hold the button for 2 milliseconds, giving the boat a speed of 2 millimeters per millisecond. It will then get 5 milliseconds to move, reaching a total distance of 10 millimeters.
|
||||
Hold the button for 3 milliseconds. After its remaining 4 milliseconds of travel time, the boat will have gone 12 millimeters.
|
||||
Hold the button for 4 milliseconds. After its remaining 3 milliseconds of travel time, the boat will have gone 12 millimeters.
|
||||
Hold the button for 5 milliseconds, causing the boat to travel a total of 10 millimeters.
|
||||
Hold the button for 6 milliseconds, causing the boat to travel a total of 6 millimeters.
|
||||
Hold the button for 7 milliseconds. That's the entire duration of the race. You never let go of the button. The boat can't move until you let you of the button. Please make sure you let go of the button so the boat gets to move. 0 millimeters.
|
||||
|
||||
Since the current record for this race is 9 millimeters, there are actually 4 different ways you could win: you could hold the button for 2, 3, 4, or 5 milliseconds at the start of the race.
|
||||
In the second race, you could hold the button for at least 4 milliseconds and at most 11 milliseconds and beat the record, a total of 8 different ways to win.
|
||||
In the third race, you could hold the button for at least 11 milliseconds and no more than 19 milliseconds and still beat the record, a total of 9 ways you could win.
|
||||
To see how much margin of error you have, determine the number of ways you can beat the record in each race; in this example, if you multiply these values together, you get 288 (4 * 8 * 9).
|
||||
Determine the number of ways you could beat the record in each race. What do you get if you multiply these numbers together?
|
||||
|
||||
https://adventofcode.com/2023/day/6
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
@@ -50,8 +19,8 @@ Distance: 9 40 200
|
||||
// distance = (bt * (bt+1))/2 + bt * (tt - 2 * bt)
|
||||
// Spend 30 minutes figuring out why I don't win any race when the boat is actually accelerating!
|
||||
fun part1(input: List<String>): Int {
|
||||
val times = input[0].split(":")[1].split(" ").filter { it.isNotBlank() }.map { it.toInt() }
|
||||
val distances = input[1].split(":")[1].split(" ").filter { it.isNotBlank() }.map { it.toInt() }
|
||||
val times = input[0].split(":")[1].splitInts()
|
||||
val distances = input[1].split(":")[1].splitInts()
|
||||
val result = times.withIndex().map { (i, tt) ->
|
||||
IntRange(1, tt - 1).count {
|
||||
it * (tt - it) > distances[i]
|
||||
|
||||
+1
-39
@@ -5,45 +5,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 7: Camel Cards ---
|
||||
Your all-expenses-paid trip turns out to be a one-way, five-minute ride in an airship. (At least it's a cool airship!) It drops you off at the edge of a vast desert and descends back to Island Island.
|
||||
"Did you bring the parts?"
|
||||
You turn around to see an Elf completely covered in white clothing, wearing goggles, and riding a large camel.
|
||||
"Did you bring the parts?" she asks again, louder this time. You aren't sure what parts she's looking for; you're here to figure out why the sand stopped.
|
||||
"The parts! For the sand, yes! Come with me; I will show you." She beckons you onto the camel.
|
||||
After riding a bit across the sands of Desert Island, you can see what look like very large rocks covering half of the horizon. The Elf explains that the rocks are all along the part of Desert Island that is directly above Island Island, making it hard to even get there. Normally, they use big machines to move the rocks and filter the sand, but the machines have broken down because Desert Island recently stopped receiving the parts they need to fix the machines.
|
||||
You've already assumed it'll be your job to figure out why the parts stopped when she asks if you can help. You agree automatically.
|
||||
Because the journey will take a few days, she offers to teach you the game of Camel Cards. Camel Cards is sort of similar to poker except it's designed to be easier to play while riding a camel.
|
||||
In Camel Cards, you get a list of hands, and your goal is to order them based on the strength of each hand. A hand consists of five cards labeled one of A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, or 2. The relative strength of each card follows this order, where A is the highest and 2 is the lowest.
|
||||
Every hand is exactly one type. From strongest to weakest, they are:
|
||||
|
||||
Five of a kind, where all five cards have the same label: AAAAA
|
||||
Four of a kind, where four cards have the same label and one card has a different label: AA8AA
|
||||
Full house, where three cards have the same label, and the remaining two cards share a different label: 23332
|
||||
Three of a kind, where three cards have the same label, and the remaining two cards are each different from any other card in the hand: TTT98
|
||||
Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label: 23432
|
||||
One pair, where two cards share one label, and the other three cards have a different label from the pair and each other: A23A4
|
||||
High card, where all cards' labels are distinct: 23456
|
||||
|
||||
Hands are primarily ordered based on type; for example, every full house is stronger than any three of a kind.
|
||||
If two hands have the same type, a second ordering rule takes effect. Start by comparing the first card in each hand. If these cards are different, the hand with the stronger first card is considered stronger. If the first card in each hand have the same label, however, then move on to considering the second card in each hand. If they differ, the hand with the higher second card wins; otherwise, continue with the third card in each hand, then the fourth, then the fifth.
|
||||
So, 33332 and 2AAAA are both four of a kind hands, but 33332 is stronger because its first card is stronger. Similarly, 77888 and 77788 are both a full house, but 77888 is stronger because its third card is stronger (and both hands have the same first and second card).
|
||||
To play Camel Cards, you are given a list of hands and their corresponding bid (your puzzle input). For example:
|
||||
32T3K 765
|
||||
T55J5 684
|
||||
KK677 28
|
||||
KTJJT 220
|
||||
QQQJA 483
|
||||
|
||||
This example shows five hands; each hand is followed by its bid amount. Each hand wins an amount equal to its bid multiplied by its rank, where the weakest hand gets rank 1, the second-weakest hand gets rank 2, and so on up to the strongest hand. Because there are five hands in this example, the strongest hand will have rank 5 and its bid will be multiplied by 5.
|
||||
So, the first step is to put the hands in order of strength:
|
||||
|
||||
32T3K is the only one pair and the other hands are all a stronger type, so it gets rank 1.
|
||||
KK677 and KTJJT are both two pair. Their first cards both have the same label, but the second card of KK677 is stronger (K vs T), so KTJJT gets rank 2 and KK677 gets rank 3.
|
||||
T55J5 and QQQJA are both three of a kind. QQQJA has a stronger first card, so it gets rank 5 and T55J5 gets rank 4.
|
||||
|
||||
Now, you can determine the total winnings of this set of hands by adding up the result of multiplying each hand's bid with its rank (765 * 1 + 220 * 2 + 28 * 3 + 684 * 4 + 483 * 5). So the total winnings in this example are 6440.
|
||||
Find the rank of every hand in your set. What are the total winnings?
|
||||
|
||||
https://adventofcode.com/2023/day/7
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+1
-25
@@ -6,31 +6,7 @@ import readInput
|
||||
|
||||
/*
|
||||
--- Day 8: Haunted Wasteland ---
|
||||
You're still riding a camel across Desert Island when you spot a sandstorm quickly approaching. When you turn to warn the Elf, she disappears before your eyes! To be fair, she had just finished warning you about ghosts a few minutes ago.
|
||||
One of the camel's pouches is labeled "maps" - sure enough, it's full of documents (your puzzle input) about how to navigate the desert. At least, you're pretty sure that's what they are; one of the documents contains a list of left/right instructions, and the rest of the documents seem to describe some kind of network of labeled nodes.
|
||||
It seems like you're meant to use the left/right instructions to navigate the network. Perhaps if you have the camel follow the same instructions, you can escape the haunted wasteland!
|
||||
After examining the maps for a bit, two nodes stick out: AAA and ZZZ. You feel like AAA is where you are now, and you have to follow the left/right instructions until you reach ZZZ.
|
||||
This format defines each node of the network individually. For example:
|
||||
RL
|
||||
|
||||
AAA = (BBB, CCC)
|
||||
BBB = (DDD, EEE)
|
||||
CCC = (ZZZ, GGG)
|
||||
DDD = (DDD, DDD)
|
||||
EEE = (EEE, EEE)
|
||||
GGG = (GGG, GGG)
|
||||
ZZZ = (ZZZ, ZZZ)
|
||||
|
||||
Starting with AAA, you need to look up the next element based on the next left/right instruction in your input. In this example, start with AAA and go right (R) by choosing the right element of AAA, CCC. Then, L means to choose the left element of CCC, ZZZ. By following the left/right instructions, you reach ZZZ in 2 steps.
|
||||
Of course, you might not find ZZZ right away. If you run out of left/right instructions, repeat the whole sequence of instructions as necessary: RL really means RLRLRLRLRLRLRLRL... and so on. For example, here is a situation that takes 6 steps to reach ZZZ:
|
||||
LLR
|
||||
|
||||
AAA = (BBB, BBB)
|
||||
BBB = (AAA, ZZZ)
|
||||
ZZZ = (ZZZ, ZZZ)
|
||||
|
||||
Starting at AAA, follow the left/right instructions. How many steps are required to reach ZZZ?
|
||||
|
||||
https://adventofcode.com/2023/day/8
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
|
||||
+6
-61
@@ -2,64 +2,11 @@ package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
|
||||
/*
|
||||
--- Day 9: Mirage Maintenance ---
|
||||
You ride the camel through the sandstorm and stop where the ghost's maps told you to stop. The sandstorm subsequently subsides, somehow seeing you standing at an oasis!
|
||||
The camel goes to get some water and you stretch your neck. As you look up, you discover what must be yet another giant floating island, this one made of metal! That must be where the parts to fix the sand machines come from.
|
||||
There's even a hang glider partially buried in the sand here; once the sun rises and heats up the sand, you might be able to use the glider and the hot air to get all the way up to the metal island!
|
||||
While you wait for the sun to rise, you admire the oasis hidden here in the middle of Desert Island. It must have a delicate ecosystem; you might as well take some ecological readings while you wait. Maybe you can report any environmental instabilities you find to someone so the oasis can be around for the next sandstorm-worn traveler.
|
||||
You pull out your handy Oasis And Sand Instability Sensor and analyze your surroundings. The OASIS produces a report of many values and how they are changing over time (your puzzle input). Each line in the report contains the history of a single value. For example:
|
||||
0 3 6 9 12 15
|
||||
1 3 6 10 15 21
|
||||
10 13 16 21 30 45
|
||||
|
||||
To best protect the oasis, your environmental report should include a prediction of the next value in each history. To do this, start by making a new sequence from the difference at each step of your history. If that sequence is not all zeroes, repeat this process, using the sequence you just generated as the input sequence. Once all of the values in your latest sequence are zeroes, you can extrapolate what the next value of the original history should be.
|
||||
In the above dataset, the first history is 0 3 6 9 12 15. Because the values increase by 3 each step, the first sequence of differences that you generate will be 3 3 3 3 3. Note that this sequence has one fewer value than the input sequence because at each step it considers two numbers from the input. Since these values aren't all zero, repeat the process: the values differ by 0 at each step, so the next sequence is 0 0 0 0. This means you have enough information to extrapolate the history! Visually, these sequences can be arranged like this:
|
||||
0 3 6 9 12 15
|
||||
3 3 3 3 3
|
||||
0 0 0 0
|
||||
|
||||
To extrapolate, start by adding a new zero to the end of your list of zeroes; because the zeroes represent differences between the two values above them, this also means there is now a placeholder in every sequence above it:
|
||||
0 3 6 9 12 15 B
|
||||
3 3 3 3 3 A
|
||||
0 0 0 0 0
|
||||
|
||||
You can then start filling in placeholders from the bottom up. A needs to be the result of increasing 3 (the value to its left) by 0 (the value below it); this means A must be 3:
|
||||
0 3 6 9 12 15 B
|
||||
3 3 3 3 3 3
|
||||
0 0 0 0 0
|
||||
|
||||
Finally, you can fill in B, which needs to be the result of increasing 15 (the value to its left) by 3 (the value below it), or 18:
|
||||
0 3 6 9 12 15 18
|
||||
3 3 3 3 3 3
|
||||
0 0 0 0 0
|
||||
|
||||
So, the next value of the first history is 18.
|
||||
Finding all-zero differences for the second history requires an additional sequence:
|
||||
1 3 6 10 15 21
|
||||
2 3 4 5 6
|
||||
1 1 1 1
|
||||
0 0 0
|
||||
|
||||
Then, following the same process as before, work out the next value in each sequence from the bottom up:
|
||||
1 3 6 10 15 21 28
|
||||
2 3 4 5 6 7
|
||||
1 1 1 1 1
|
||||
0 0 0 0
|
||||
|
||||
So, the next value of the second history is 28.
|
||||
The third history requires even more sequences, but its next value can be found the same way:
|
||||
10 13 16 21 30 45 68
|
||||
3 3 5 9 15 23
|
||||
0 2 4 6 8
|
||||
2 2 2 2
|
||||
0 0 0
|
||||
|
||||
So, the next value of the third history is 68.
|
||||
If you find the next value for each history in this example and add them together, you get 114.
|
||||
Analyze your OASIS report and extrapolate the next value for each history. What is the sum of these extrapolated values?
|
||||
|
||||
https://adventofcode.com/2023/day/9
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
@@ -72,23 +19,21 @@ fun main() {
|
||||
fun delta(i: List<Int>): Int {
|
||||
if (i.all { it == 0 }) return 0
|
||||
val d = i.zipWithNext { a, b -> b - a }
|
||||
val p = delta(d)
|
||||
return d.last() + p
|
||||
return d.last() + delta(d)
|
||||
}
|
||||
|
||||
fun delta2(i: List<Int>): Int {
|
||||
if (i.all { it == 0 }) return 0
|
||||
val d = i.zipWithNext { a, b -> b - a }
|
||||
val p = delta2(d)
|
||||
return d.first() - p
|
||||
return d.first() - delta2(d)
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
return input.map { it.split(" ").map { it.toInt() } }.sumOf { delta(it) + it.last() }
|
||||
return input.map { it.splitInts() }.sumOf { delta(it) + it.last() }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
return input.map { it.split(" ").map { it.toInt() } }.sumOf { it.first() - delta2(it) }
|
||||
return input.map { it.splitInts() }.sumOf { it.first() - delta2(it) }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 10: Pipe Maze ---
|
||||
https://adventofcode.com/2023/day/10
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
..F7.
|
||||
.FJ|.
|
||||
SJ.L7
|
||||
|F--J
|
||||
LJ...
|
||||
"""
|
||||
|
||||
val inlineTestInput2a = """
|
||||
.F----7F7F7F7F-7....
|
||||
.|F--7||||||||FJ....
|
||||
.||.FJ||||||||L7....
|
||||
FJL7L7LJLJ||LJ.L-7..
|
||||
L--J.L7...LJS7F-7L7.
|
||||
....F-J..F7FJ|L7L7L7
|
||||
....L7.F7||L7|.L7L7|
|
||||
.....|FJLJ|FJ|F7|.LJ
|
||||
....FJL-7.||.||||...
|
||||
....L---J.LJ.LJLJ...
|
||||
"""
|
||||
|
||||
val inlineTestInput2 = """
|
||||
FF7FSF7F7F7F7F7F---7
|
||||
L|LJ||||||||||||F--J
|
||||
FL-7LJLJ||||||LJL-77
|
||||
F--JF--7||LJLJ7F7FJ-
|
||||
L---JF-JLJ.||-FJLJJ7
|
||||
|F|F-JF---7F7-L7L|7|
|
||||
|FFJF7L7F-JF7|JL---7
|
||||
7-L-JL7||F7|L7F-7F7|
|
||||
L.L7LFJ|||||FJL7||LJ
|
||||
L7JLJL-JLJLJL--JLJ.L
|
||||
"""
|
||||
|
||||
val north = 1
|
||||
val south = 2
|
||||
val west = 4
|
||||
val east = 8
|
||||
val dirMask = north or south or west or east
|
||||
val start = 16
|
||||
|
||||
val directions = mapOf(
|
||||
north to (0 to -1),
|
||||
south to (0 to 1),
|
||||
west to (-1 to 0),
|
||||
east to (1 to 0)
|
||||
)
|
||||
|
||||
val mapping = mapOf(
|
||||
'S' to start,
|
||||
'F' to (south or east),
|
||||
'7' to (west or south),
|
||||
'J' to (north or west),
|
||||
'L' to (east or north),
|
||||
'-' to (west or east),
|
||||
'|' to (north or south),
|
||||
'.' to 0
|
||||
)
|
||||
|
||||
fun swapDirections(code: Int) = ((code and (north or west)) shl 1) or ((code and (south or east)) shr 1)
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val startPos = grid.findMatches { it == 'S' }.first()
|
||||
grid.apply { (mapping[it]!! + 32).toChar() }
|
||||
val currentDirs = directions.filter {
|
||||
(grid[startPos.first + it.value.first, startPos.second + it.value.second].code and swapDirections(it.key)) != 0
|
||||
}.keys.toTypedArray()
|
||||
val locations = arrayOf(startPos, startPos)
|
||||
var distance = 0
|
||||
var notHit = true
|
||||
do {
|
||||
distance++
|
||||
for (i in locations.indices) {
|
||||
val dir = currentDirs[i]
|
||||
val dirRel = directions[dir]!!
|
||||
val loc = locations[i]
|
||||
|
||||
val newLoc = loc.first + dirRel.first to loc.second + dirRel.second
|
||||
if (grid[newLoc.first, newLoc.second] == 'Z') {
|
||||
notHit = false
|
||||
break
|
||||
}
|
||||
currentDirs[i] = (grid[newLoc.first, newLoc.second].code xor swapDirections(dir)) and dirMask
|
||||
locations[i] = newLoc
|
||||
grid[newLoc.first, newLoc.second] = 'Z'
|
||||
}
|
||||
} while (notHit)
|
||||
return distance
|
||||
}
|
||||
|
||||
fun floodFill(grid: Array<IntArray>, c: Int, r: Int) {
|
||||
grid[r][c] = 2
|
||||
CharGrid.PLUS_POS.map { it.translate(c, r) }
|
||||
// .filter { it.dc in grid[0].indices && it.dr in grid.indices }
|
||||
.filter { grid[it.dr][it.dc] == 0 }.forEach { floodFill(grid, it.dc, it.dr) }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val startPos = grid.findMatches { it == 'S' }.first()
|
||||
grid.apply { (mapping[it]!! + 32).toChar() }
|
||||
val currentDirs = directions.filter {
|
||||
(grid[startPos.first + it.value.first, startPos.second + it.value.second].code and swapDirections(it.key)) != 0
|
||||
}.keys.toTypedArray()
|
||||
|
||||
val outline = Array(grid.height * 3) { IntArray(grid.width * 3) }
|
||||
val locations = arrayOf(startPos, startPos)
|
||||
var notHit = true
|
||||
do {
|
||||
for (i in locations.indices) {
|
||||
val dir = currentDirs[i]
|
||||
val dirRel = directions[dir]!!
|
||||
val loc = locations[i]
|
||||
|
||||
outline[loc.second * 3 + 1][loc.first * 3 + 1] = 1
|
||||
outline[loc.second * 3 + 1 + dirRel.second][loc.first * 3 + 1 + dirRel.first] = 1
|
||||
outline[loc.second * 3 + 1 + dirRel.second * 2][loc.first * 3 + 1 + dirRel.first * 2] = 1
|
||||
|
||||
val newLoc = loc.first + dirRel.first to loc.second + dirRel.second
|
||||
if (grid[newLoc.first, newLoc.second] == 'Z') {
|
||||
notHit = false
|
||||
break
|
||||
}
|
||||
val newDir = (grid[newLoc.first, newLoc.second].code xor swapDirections(dir)) and dirMask
|
||||
val newDirRel = directions[newDir]!!
|
||||
|
||||
outline[newLoc.second * 3 + 1][newLoc.first * 3 + 1] = 1
|
||||
outline[newLoc.second * 3 + 1 + newDirRel.second][newLoc.first * 3 + 1 + newDirRel.first] = 1
|
||||
|
||||
currentDirs[i] = newDir
|
||||
locations[i] = newLoc
|
||||
grid[newLoc.first, newLoc.second] = 'Z'
|
||||
}
|
||||
} while (notHit)
|
||||
|
||||
// guess the inside
|
||||
val ipos = 1 to 1
|
||||
if (outline[startPos.second * 3 + 1 + ipos.second][startPos.first * 3 + 1 + ipos.first] != 0) {
|
||||
println("Bah!")
|
||||
}
|
||||
floodFill(outline, startPos.first * 3 + 1 + ipos.first, startPos.second * 3 + 1 + ipos.second)
|
||||
|
||||
var insideCount = 0
|
||||
var outsideCount = 0
|
||||
grid.applyWithPos { grid, col, row ->
|
||||
if (grid[col, row] != 'Z') {
|
||||
if (outline[row * 3][col * 3] == 2) insideCount++ else outsideCount++
|
||||
}
|
||||
null
|
||||
}
|
||||
return insideCount
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day10_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
//val testInputPart2Result = part2(testInput2)
|
||||
//println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 8)
|
||||
//check(testInputPart2Result == 10)
|
||||
|
||||
val input = readInput("aoc2023/Day10")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
import kotlin.math.abs
|
||||
|
||||
/*
|
||||
--- Day 11: Cosmic Expansion ---
|
||||
https://adventofcode.com/2023/day/11
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
...#......
|
||||
.......#..
|
||||
#.........
|
||||
..........
|
||||
......#...
|
||||
.#........
|
||||
.........#
|
||||
..........
|
||||
.......#..
|
||||
#...#.....
|
||||
"""
|
||||
|
||||
fun parts(input: List<String>, exp: Int): Long {
|
||||
val image = CharGrid(input, '.')
|
||||
val exCol = IntArray(image.width)
|
||||
val exRow = IntArray(image.height)
|
||||
var ex = 0
|
||||
for (c in exCol.indices) {
|
||||
var hasGalaxies = false
|
||||
for (r in 0 until image.height) {
|
||||
if (image[c, r] == '#') hasGalaxies = true
|
||||
}
|
||||
if (!hasGalaxies) ex += exp
|
||||
exCol[c] = ex
|
||||
}
|
||||
ex = 0
|
||||
for (r in exRow.indices) {
|
||||
var hasGalaxies = false
|
||||
for (c in 0 until image.width) {
|
||||
if (image[c, r] == '#') hasGalaxies = true
|
||||
}
|
||||
if (!hasGalaxies) ex += exp
|
||||
exRow[r] = ex
|
||||
}
|
||||
val gal = image.findMatches { it == '#' }.map { it.first + exCol[it.first] to it.second + exRow[it.second] }.toTypedArray()
|
||||
var minSum = 0L
|
||||
var pairCount = 0
|
||||
for (g1 in 0 until gal.lastIndex) {
|
||||
for (g2 in g1 + 1..gal.lastIndex) {
|
||||
val d = abs(gal[g1].first - gal[g2].first) + abs(gal[g1].second - gal[g2].second)
|
||||
minSum += d
|
||||
pairCount++
|
||||
}
|
||||
}
|
||||
return minSum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day11_test")
|
||||
val testInputPart1Result = parts(testInput, 1)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2aResult = parts(testInput, 9)
|
||||
println("Part 2a Test: $testInputPart2aResult")
|
||||
val testInputPart2Result = parts(testInput, 99)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 374L)
|
||||
check(testInputPart2aResult == 1030L)
|
||||
check(testInputPart2Result == 8410L)
|
||||
|
||||
val input = readInput("aoc2023/Day11")
|
||||
parts(input, 1).println()
|
||||
parts(input, 1000000 - 1).println()
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 12: Hot Springs ---
|
||||
https://adventofcode.com/2023/day/12
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
???.### 1,1,3
|
||||
.??..??...?##. 1,1,3
|
||||
?#?#?#?#?#?#?#? 1,3,1,6
|
||||
????.#...#... 4,1,1
|
||||
????.######..#####. 1,6,5
|
||||
?###???????? 3,2,1
|
||||
"""
|
||||
|
||||
val memo = HashMap<Int, Long>()
|
||||
|
||||
fun fits(input: String, regs: IntArray, inputPos: Int, regPos: Int): Boolean {
|
||||
if (regPos > regs.lastIndex || inputPos + regs[regPos] > input.length) return false
|
||||
var ip = inputPos
|
||||
var e = regs[regPos]
|
||||
if (input[ip] == '.') ip++
|
||||
while ((e > 0) && ip <= input.lastIndex && input[ip++] != '.') {
|
||||
e--
|
||||
}
|
||||
if (e > 0) return false
|
||||
return ip > input.lastIndex || input[ip] != '#'
|
||||
}
|
||||
|
||||
fun count(input: String, regs: IntArray, inputPos: Int, regPos: Int): Long {
|
||||
val memVal = memo[inputPos * 1000 + regPos]
|
||||
if (memVal != null) return memVal
|
||||
|
||||
//println("$inputPos, $regPos, ${input.substring(inputPos)}")
|
||||
if (inputPos > input.lastIndex && regPos > regs.lastIndex) return 1
|
||||
if (inputPos > input.lastIndex) return 0
|
||||
var result = 0L
|
||||
if (input[inputPos] != '#') {
|
||||
result += count(input, regs, inputPos + 1, regPos).also { memo[(inputPos + 1) * 1000 + regPos] = it }
|
||||
}
|
||||
if (input[inputPos] != '.') {
|
||||
if (fits(input, regs, inputPos, regPos)) {
|
||||
val p = inputPos + regs[regPos] + 1
|
||||
result += count(input, regs, p, regPos + 1).also { memo[p * 1000 + regPos + 1] = it }
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val results = input.map {
|
||||
val (springline, parms) = it.split(" ")
|
||||
val reduced = springline.replace("\\.+".toRegex(), ".").trim { it == '.' }
|
||||
val regs = parms.split(",").map { it.toInt() }.toIntArray()
|
||||
memo.clear()
|
||||
count(reduced, regs, 0, 0)
|
||||
}
|
||||
return results.sum()
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val results = input.map {
|
||||
val (springline, parms) = it.split(" ")
|
||||
val reduced = springline.replace("\\.+".toRegex(), ".")
|
||||
val regs = parms.split(",").map { it.toInt() }.toList()
|
||||
val regs5 = List(5) { regs }.flatten().toIntArray()
|
||||
val red5 = List(5) { reduced }.joinToString("?").trim { it == '.' }
|
||||
memo.clear()
|
||||
count(red5, regs5, 0, 0)
|
||||
}
|
||||
return results.sum()
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day12_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 21L)
|
||||
check(testInputPart2Result == 525152L)
|
||||
|
||||
val input = readInput("aoc2023/Day12")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 13: Point of Incidence ---
|
||||
https://adventofcode.com/2023/day/13
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
#.##..##.
|
||||
..#.##.#.
|
||||
##......#
|
||||
##......#
|
||||
..#.##.#.
|
||||
..##..##.
|
||||
#.#.##.#.
|
||||
|
||||
#...##..#
|
||||
#....#..#
|
||||
..##..###
|
||||
#####.##.
|
||||
#####.##.
|
||||
..##..###
|
||||
#....#..#
|
||||
"""
|
||||
|
||||
fun mirrored(bits: IntArray, size: Int): Int {
|
||||
for (p1 in 0 until size - 1) {
|
||||
var p2 = 0
|
||||
var match = true
|
||||
while (p1 >= p2 && p1 + p2 + 1 < size) {
|
||||
if (bits[p1 - p2] != bits[p1 + p2 + 1]) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
p2++
|
||||
}
|
||||
if (match) return p1 + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun smudged(bits: IntArray, size: Int): Int {
|
||||
for (p1 in 0 until size - 1) {
|
||||
var smudges = 0
|
||||
var p2 = 0
|
||||
while (p1 >= p2 && p1 + p2 + 1 < size) {
|
||||
smudges += (bits[p1 - p2] xor bits[p1 + p2 + 1]).countOneBits()
|
||||
if (smudges > 1) break
|
||||
p2++
|
||||
}
|
||||
if (smudges == 1) return p1 + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val tmpList = ArrayList<String>()
|
||||
var result = 0
|
||||
for ((idx, i) in input.withIndex()) {
|
||||
if (i.isNotBlank()) {
|
||||
tmpList.add(i)
|
||||
}
|
||||
if (i.isBlank() || (idx == input.lastIndex && tmpList.isNotEmpty())) {
|
||||
val grid = CharGrid(tmpList)
|
||||
val vertSA = IntArray(grid.width)
|
||||
val horzSA = IntArray(grid.height)
|
||||
for (c in 0 until grid.width) {
|
||||
for (r in 0 until grid.height) {
|
||||
vertSA[c] = vertSA[c] * 2 + (if (grid[c, r] == '#') 1 else 0)
|
||||
}
|
||||
}
|
||||
for (r in 0 until grid.height) {
|
||||
for (c in 0 until grid.width) {
|
||||
horzSA[r] = horzSA[r] * 2 + if (grid[c, r] == '#') 1 else 0
|
||||
}
|
||||
}
|
||||
val vref = mirrored(vertSA, grid.width)
|
||||
if (vref > 0) {
|
||||
result += vref
|
||||
} else {
|
||||
result += mirrored(horzSA, grid.height) * 100
|
||||
}
|
||||
|
||||
tmpList.clear()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val tmpList = ArrayList<String>()
|
||||
var result = 0
|
||||
for ((idx, i) in input.withIndex()) {
|
||||
if (i.isNotBlank()) {
|
||||
tmpList.add(i)
|
||||
}
|
||||
if (i.isBlank() || (idx == input.lastIndex && tmpList.isNotEmpty())) {
|
||||
val grid = CharGrid(tmpList)
|
||||
val vertSA = IntArray(grid.width)
|
||||
val horzSA = IntArray(grid.height)
|
||||
for (c in 0 until grid.width) {
|
||||
for (r in 0 until grid.height) {
|
||||
vertSA[c] = vertSA[c] * 2 + (if (grid[c, r] == '#') 1 else 0)
|
||||
}
|
||||
}
|
||||
for (r in 0 until grid.height) {
|
||||
for (c in 0 until grid.width) {
|
||||
horzSA[r] = horzSA[r] * 2 + if (grid[c, r] == '#') 1 else 0
|
||||
}
|
||||
}
|
||||
val vref = smudged(vertSA, grid.width)
|
||||
if (vref > 0) {
|
||||
result += vref
|
||||
} else {
|
||||
result += smudged(horzSA, grid.height) * 100
|
||||
}
|
||||
|
||||
tmpList.clear()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day13_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 405)
|
||||
check(testInputPart2Result == 400)
|
||||
|
||||
val input = readInput("aoc2023/Day13")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 14: Parabolic Reflector Dish ---
|
||||
https://adventofcode.com/2023/day/14
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
O....#....
|
||||
O.OO#....#
|
||||
.....##...
|
||||
OO.#O....O
|
||||
.O.....O#.
|
||||
O.#..O.#.#
|
||||
..O..#O..O
|
||||
.......O..
|
||||
#....###..
|
||||
#OO..#....
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input, borderChar = '#')
|
||||
for (r in 0 until grid.height) {
|
||||
for (c in 0 until grid.width) {
|
||||
var rr = r
|
||||
while (grid[c, rr] == '.' && grid[c, rr + 1] == 'O') {
|
||||
grid[c, rr] = 'O'
|
||||
grid[c, rr + 1] = '.'
|
||||
rr--
|
||||
}
|
||||
}
|
||||
}
|
||||
var sum = 0
|
||||
for (r in 0 until grid.height) {
|
||||
for (c in 0 until grid.width) {
|
||||
if (grid[c, r] == 'O') {
|
||||
sum += grid.height - r
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input, borderChar = '#')
|
||||
var cycleCount = 0
|
||||
val memo = HashMap<CharGrid, MutableSet<Int>>()
|
||||
var trackmode = false
|
||||
var cycleOffset = 0
|
||||
val cycle = ArrayList<CharGrid>()
|
||||
do {
|
||||
for (r in 0 until grid.height) {
|
||||
for (c in 0 until grid.width) {
|
||||
var rr = r
|
||||
while (grid[c, rr] == '.' && grid[c, rr + 1] == 'O') {
|
||||
grid[c, rr] = 'O'
|
||||
grid[c, rr + 1] = '.'
|
||||
rr--
|
||||
}
|
||||
}
|
||||
}
|
||||
for (c in 0 until grid.width) {
|
||||
for (r in 0 until grid.height) {
|
||||
var cc = c
|
||||
while (grid[cc, r] == '.' && grid[cc + 1, r] == 'O') {
|
||||
grid[cc, r] = 'O'
|
||||
grid[cc + 1, r] = '.'
|
||||
cc--
|
||||
}
|
||||
}
|
||||
}
|
||||
for (r in grid.height - 1 downTo 0) {
|
||||
for (c in 0 until grid.width) {
|
||||
var rr = r
|
||||
while (grid[c, rr] == '.' && grid[c, rr - 1] == 'O') {
|
||||
grid[c, rr] = 'O'
|
||||
grid[c, rr - 1] = '.'
|
||||
rr++
|
||||
}
|
||||
}
|
||||
}
|
||||
for (c in grid.width - 1 downTo 0) {
|
||||
for (r in 0 until grid.height) {
|
||||
var cc = c
|
||||
while (grid[cc, r] == '.' && grid[cc - 1, r] == 'O') {
|
||||
grid[cc, r] = 'O'
|
||||
grid[cc - 1, r] = '.'
|
||||
cc++
|
||||
}
|
||||
}
|
||||
}
|
||||
val set = memo.getOrPut(grid.copyOf()) { HashSet() }
|
||||
if (set.isNotEmpty()) {
|
||||
if (trackmode == false) {
|
||||
cycleOffset = cycleCount
|
||||
}
|
||||
trackmode = true
|
||||
}
|
||||
if (set.size == 2) break
|
||||
if (trackmode) {
|
||||
cycle.add(grid.copyOf())
|
||||
}
|
||||
cycleCount++
|
||||
set.add(cycleCount)
|
||||
|
||||
} while (true)
|
||||
|
||||
println("cycleCount $cycleCount, cycleOffset $cycleOffset, cycleLength ${cycle.size}")
|
||||
val offset = (1_000_000_000 - cycleOffset - 1) % cycle.size
|
||||
println("offset $offset")
|
||||
val pickGrid = cycle[offset]
|
||||
println(cycleCount)
|
||||
var sum = 0
|
||||
for (r in 0 until pickGrid.height) {
|
||||
for (c in 0 until pickGrid.width) {
|
||||
if (pickGrid[c, r] == 'O') {
|
||||
sum += pickGrid.height - r
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day14_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 136)
|
||||
check(testInputPart2Result == 64)
|
||||
|
||||
val input = readInput("aoc2023/Day14")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 15: Lens Library ---
|
||||
https://adventofcode.com/2023/day/15
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
|
||||
"""
|
||||
|
||||
fun hash(str: String): Int = str.map { it.code }.fold(0) { acc, c -> ((acc + c) * 17) and 0xff }
|
||||
fun part1(input: List<String>): Int {
|
||||
return input.first().split(",").sumOf { hash(it) }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val boxes = Array<MutableMap<String, Int>>(256) { LinkedHashMap() }
|
||||
input.first().split(",").forEach { it ->
|
||||
if (it.contains('=')) {
|
||||
val (label, value) = it.split("=")
|
||||
boxes[hash(label)][label] = value.toInt()
|
||||
} else {
|
||||
val label = it.removeSuffix("-")
|
||||
boxes[hash(label)].remove(label)
|
||||
}
|
||||
}
|
||||
return boxes.mapIndexed { boxno, contents ->
|
||||
contents.map { entry -> entry.value }
|
||||
.mapIndexed { index, value -> (boxno + 1) * value * (index + 1) }.sum()
|
||||
}.sum()
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day15_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 1320)
|
||||
check(testInputPart2Result == 145)
|
||||
|
||||
val input = readInput("aoc2023/Day15")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 16: The Floor Will Be Lava ---
|
||||
https://adventofcode.com/2023/day/16
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
.|...\....
|
||||
|.-.\.....
|
||||
.....|-...
|
||||
........|.
|
||||
..........
|
||||
.........\
|
||||
..../.\\..
|
||||
.-.-/..|..
|
||||
.|....-|.\
|
||||
..//.|....
|
||||
"""
|
||||
|
||||
fun runBeam(origGrid: CharGrid, startPos: Pair<RelPos, RelPos>): Int {
|
||||
val grid = origGrid.copyOf()
|
||||
val rays = ArrayDeque<Pair<RelPos, RelPos>>()
|
||||
rays.add(startPos)
|
||||
while (rays.isNotEmpty()) {
|
||||
var (rayPos, rayDir) = rays.removeFirst()
|
||||
while (origGrid[rayPos.dc, rayPos.dr] == '/' || origGrid[rayPos.dc, rayPos.dr] == '\\' || !grid[rayPos.dc, rayPos.dr].isDigit() || (grid[rayPos.dc, rayPos.dr].code and (if (rayDir.dc != 0) 1 else 2) == 0)) {
|
||||
grid[rayPos.dc, rayPos.dr] =
|
||||
((if (grid[rayPos.dc, rayPos.dr].isDigit()) grid[rayPos.dc, rayPos.dr] else '0').code or (if (rayDir.dc != 0) 1 else 2)).toChar()
|
||||
when (origGrid[rayPos.dc, rayPos.dr]) {
|
||||
'\\' -> rayDir = RelPos(rayDir.dr, rayDir.dc)
|
||||
'/' -> rayDir = RelPos(-rayDir.dr, -rayDir.dc)
|
||||
'|' -> if (rayDir.dc != 0) {
|
||||
rayDir = RelPos(0, -1)
|
||||
rays.add(rayPos.translate(0, 1) to RelPos(0, 1))
|
||||
}
|
||||
|
||||
'-' -> if (rayDir.dr != 0) {
|
||||
rayDir = RelPos(-1, 0)
|
||||
rays.add(rayPos.translate(1, 0) to RelPos(1, 0))
|
||||
}
|
||||
}
|
||||
rayPos = rayPos.translate(rayDir)
|
||||
}
|
||||
}
|
||||
return grid.findMatches { it.isDigit() }.count()
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val origGrid = CharGrid(input, '3')
|
||||
return runBeam(origGrid, RelPos(0, 0) to RelPos(1, 0))
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val origGrid = CharGrid(input, '3')
|
||||
|
||||
var result = 0
|
||||
for (tc in 0 until origGrid.width) {
|
||||
result = result.coerceAtLeast(runBeam(origGrid, RelPos(tc, 0) to RelPos(0, 1)))
|
||||
result = result.coerceAtLeast(runBeam(origGrid, RelPos(tc, origGrid.height - 1) to RelPos(0, -1)))
|
||||
}
|
||||
for (tr in 0 until origGrid.height) {
|
||||
result = result.coerceAtLeast(runBeam(origGrid, RelPos(0, tr) to RelPos(1, 0)))
|
||||
result = result.coerceAtLeast(runBeam(origGrid, RelPos(origGrid.width - 1, tr) to RelPos(-1, 0)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day16_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 46)
|
||||
check(testInputPart2Result == 51)
|
||||
|
||||
val input = readInput("aoc2023/Day16")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
|
||||
/*
|
||||
--- Day 17: Clumsy Crucible ---
|
||||
https://adventofcode.com/2023/day/17
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
2413432311323
|
||||
3215453535623
|
||||
3255245654254
|
||||
3446585845452
|
||||
4546657867536
|
||||
1438598798454
|
||||
4457876987766
|
||||
3637877979653
|
||||
4654967986887
|
||||
4564679986453
|
||||
1224686865563
|
||||
2546548887735
|
||||
4322674655533
|
||||
"""
|
||||
|
||||
var best = Int.MAX_VALUE
|
||||
var grid = CharGrid(arrayOf(charArrayOf(' ')))
|
||||
var bestHeat = IntArray(0)
|
||||
|
||||
data class LavaContext(val c: Int, val r: Int, val dir: RelPos, val dirCount: Int, val heatloss: Int, val parent: LavaContext? = null) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LavaContext
|
||||
|
||||
if (c != other.c) return false
|
||||
if (r != other.r) return false
|
||||
if (dir != other.dir) return false
|
||||
if (dirCount != other.dirCount) return false
|
||||
if (heatloss != other.heatloss) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = c
|
||||
result = 31 * result + r
|
||||
result = 31 * result + dir.hashCode()
|
||||
result = 31 * result + dirCount
|
||||
result = 31 * result + heatloss
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val contexts = PriorityQueue(compareBy<LavaContext> { it.heatloss - it.c - it.r })
|
||||
val visitCache = HashSet<LavaContext>()
|
||||
|
||||
fun astar(minSame: Int, maxSame: Int) {
|
||||
while (contexts.isNotEmpty()) {
|
||||
do {
|
||||
val ctx = contexts.remove()!!
|
||||
if (ctx.heatloss >= best) break
|
||||
val newHeatLoss = ctx.heatloss + grid[ctx.c, ctx.r].digitToInt()
|
||||
if (newHeatLoss > best) break
|
||||
if (ctx.c == grid.width - 1 && ctx.r == grid.height - 1) {
|
||||
if (ctx.dirCount >= minSame) {
|
||||
best = best.coerceAtMost(newHeatLoss)
|
||||
val path = grid.copyOf()
|
||||
var bt: LavaContext? = ctx
|
||||
while (bt != null) {
|
||||
path[bt.c, bt.r] = '*'
|
||||
bt = bt.parent
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
val p = ctx.r * grid.width + ctx.c
|
||||
if (newHeatLoss - 10 > bestHeat[p]) break
|
||||
bestHeat[p] = min(bestHeat[p], newHeatLoss)
|
||||
val newDirs = (grid.matchRelative(ctx.c, ctx.r, CharGrid.PLUS_POS) { it != '#' }
|
||||
.filter { ctx.dir.dc != -it.dc || ctx.dir.dr != -it.dr })
|
||||
newDirs.forEach {
|
||||
val take = ((it == ctx.dir) && (ctx.dirCount + 1 < maxSame)) ||
|
||||
((it != ctx.dir) && (ctx.dirCount + 1 > minSame))
|
||||
if (take) {
|
||||
val newDirCount = if (it == ctx.dir) ctx.dirCount + 1 else 0
|
||||
val newCtx = LavaContext(ctx.c + it.dc, ctx.r + it.dr, it, newDirCount, newHeatLoss, ctx)
|
||||
if (!visitCache.contains(newCtx)) {
|
||||
visitCache.add(newCtx)
|
||||
contexts.add(newCtx)
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (false)
|
||||
}
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
grid = CharGrid(input, '#')
|
||||
best = (grid.width + grid.height) * 9
|
||||
bestHeat = IntArray(grid.width * grid.height) { best }
|
||||
val context = LavaContext(0, 0, RelPos(0, 0), 1, -grid[0, 0].digitToInt())
|
||||
visitCache.clear()
|
||||
contexts.clear()
|
||||
contexts.add(context)
|
||||
astar(0, 3)
|
||||
return best
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
grid = CharGrid(input, '#')
|
||||
best = (grid.width + grid.height) * 9
|
||||
bestHeat = IntArray(grid.width * grid.height) { best }
|
||||
val context = LavaContext(0, 0, RelPos(0, 0), 4, -grid[0, 0].digitToInt())
|
||||
visitCache.clear()
|
||||
contexts.clear()
|
||||
contexts.add(context)
|
||||
astar(3, 10)
|
||||
return best
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day17_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 102)
|
||||
check(testInputPart2Result == 94)
|
||||
|
||||
val input = readInput("aoc2023/Day17")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 18: Lavaduct Lagoon ---
|
||||
https://adventofcode.com/2023/day/18
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
R 6 (#70c710)
|
||||
D 5 (#0dc571)
|
||||
L 2 (#5713f0)
|
||||
D 2 (#d2c081)
|
||||
R 2 (#59c680)
|
||||
D 2 (#411b91)
|
||||
L 5 (#8ceee2)
|
||||
U 2 (#caa173)
|
||||
L 1 (#1b58a2)
|
||||
U 2 (#caa171)
|
||||
R 2 (#7807d2)
|
||||
U 3 (#a77fa3)
|
||||
L 2 (#015232)
|
||||
U 2 (#7a21e3)
|
||||
"""
|
||||
|
||||
data class DigInst(val cmd: String, val len: Int)
|
||||
|
||||
fun calcArea(insts: List<DigInst>): Long {
|
||||
val polygon = ArrayList<Pair<Long, Long>>()
|
||||
var x = 0L
|
||||
var y = 0L
|
||||
var sumOutline = 0L
|
||||
for (i in insts) {
|
||||
when (i.cmd) {
|
||||
"R" -> x += i.len
|
||||
"D" -> y += i.len
|
||||
"L" -> x -= i.len
|
||||
"U" -> y -= i.len
|
||||
}
|
||||
sumOutline += i.len
|
||||
polygon.add(x to y)
|
||||
}
|
||||
var area = 0L
|
||||
for (i in 0 until polygon.size) {
|
||||
val j = (i + 1) % polygon.size
|
||||
area += (polygon[i].second + polygon[j].second) * (polygon[i].first - polygon[j].first)
|
||||
}
|
||||
return (area + sumOutline) / 2 + 1
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val insts = input.map {
|
||||
val (cmd, len, color) = "([RDLU]) (\\d+) \\(#([a-z0-f]+)\\)".toRegex().matchEntire(it)!!.destructured
|
||||
DigInst(cmd, len.toInt())
|
||||
}
|
||||
return calcArea(insts)
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val insts = input.map {
|
||||
val (cmd, len, color) = "([RDLU]) (\\d+) \\(#([a-z0-f]+)\\)".toRegex().matchEntire(it)!!.destructured
|
||||
DigInst("RDLU"[(color.toInt(16) and 3)].toString(), color.toInt(16) shr 4)
|
||||
}
|
||||
return calcArea(insts)
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day18_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 62L)
|
||||
check(testInputPart2Result == 952408144115L)
|
||||
|
||||
val input = readInput("aoc2023/Day18")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 19: Aplenty ---
|
||||
https://adventofcode.com/2023/day/19
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
px{a<2006:qkq,m>2090:A,rfg}
|
||||
pv{a>1716:R,A}
|
||||
lnx{m>1548:A,A}
|
||||
rfg{s<537:gd,x>2440:R,A}
|
||||
qs{s>3448:A,lnx}
|
||||
qkq{x<1416:A,crn}
|
||||
crn{x>2662:A,R}
|
||||
in{s<1351:px,qqz}
|
||||
qqz{s>2770:qs,m<1801:hdj,R}
|
||||
gd{a>3333:R,R}
|
||||
hdj{m>838:A,pv}
|
||||
|
||||
{x=787,m=2655,a=1222,s=2876}
|
||||
{x=1679,m=44,a=2067,s=496}
|
||||
{x=2036,m=264,a=79,s=2244}
|
||||
{x=2461,m=1339,a=466,s=291}
|
||||
{x=2127,m=1623,a=2188,s=1013}
|
||||
"""
|
||||
|
||||
data class RuleCondition(val attr: Int = 0, val isSmaller: Boolean = false, val value: Int = 0, val follow: String)
|
||||
|
||||
fun createMap(input: List<String>): Pair<Int, Map<String, List<RuleCondition>>> {
|
||||
val ruleMap = HashMap<String, List<RuleCondition>>()
|
||||
var p = 0
|
||||
do {
|
||||
"([a-z]+)\\{(.*)}".toRegex().matchEntire(input[p])?.destructured
|
||||
?.let { (id, rules) ->
|
||||
val rulesList = rules.split(",").map {
|
||||
if (it.contains("<")) {
|
||||
val (attr, rest) = it.split("<")
|
||||
val (value, branchId) = rest.split(":")
|
||||
RuleCondition("xmas".indexOf(attr), true, value.toInt(), branchId)
|
||||
} else if (it.contains(">")) {
|
||||
val (attr, rest) = it.split(">")
|
||||
val (value, branchId) = rest.split(":")
|
||||
RuleCondition("xmas".indexOf(attr), false, value.toInt(), branchId)
|
||||
} else {
|
||||
RuleCondition(follow = it)
|
||||
}
|
||||
}
|
||||
ruleMap[id] = rulesList
|
||||
}
|
||||
if (input[p].isBlank()) break
|
||||
p++
|
||||
} while (true)
|
||||
return p to ruleMap
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val (p, ruleMap) = createMap(input)
|
||||
var sum = 0
|
||||
for (pp in p + 1 until input.size) {
|
||||
val attrs =
|
||||
input[pp].split("=", ",", "x", "m", "a", "s", "{", "}").filter { it.isNotBlank() }.map { it.toInt() }
|
||||
.toIntArray()
|
||||
var ruleId = "in"
|
||||
while (ruleId != "A" && ruleId != "R") {
|
||||
val conditions = ruleMap[ruleId]!!
|
||||
for (cond in conditions) {
|
||||
if (cond.isSmaller) {
|
||||
if (attrs[cond.attr] < cond.value) {
|
||||
ruleId = cond.follow
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if (attrs[cond.attr] > cond.value) {
|
||||
ruleId = cond.follow
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ruleId == "A") {
|
||||
sum += attrs.sum()
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun recurse(ruleMap: Map<String, List<RuleCondition>>, ranges: List<IntRange>, ruleId: String): Long {
|
||||
if (ruleId == "A") {
|
||||
return ranges.map { (it.last - it.first + 1).coerceAtLeast(0).toLong() }.reduce(Long::times)
|
||||
}
|
||||
if (ruleId == "R") return 0
|
||||
var result = 0L
|
||||
val conditions = ruleMap[ruleId]!!
|
||||
var currRanges = ranges
|
||||
for (cond in conditions) {
|
||||
if (cond.value != 0) {
|
||||
if (cond.isSmaller) {
|
||||
val subRange = currRanges.mapIndexed { index, intRange ->
|
||||
if (index != cond.attr) intRange else
|
||||
IntRange(intRange.first, intRange.last.coerceAtMost(cond.value - 1))
|
||||
}
|
||||
result += recurse(ruleMap, subRange, cond.follow)
|
||||
currRanges = currRanges.mapIndexed { index, intRange ->
|
||||
if (index != cond.attr) intRange else
|
||||
IntRange(intRange.first.coerceAtLeast(cond.value), intRange.last)
|
||||
}
|
||||
} else {
|
||||
val subRange = currRanges.mapIndexed { index, intRange ->
|
||||
if (index != cond.attr) intRange else
|
||||
IntRange(intRange.first.coerceAtLeast(cond.value + 1), intRange.last)
|
||||
}
|
||||
result += recurse(ruleMap, subRange, cond.follow)
|
||||
currRanges = currRanges.mapIndexed { index, intRange ->
|
||||
if (index != cond.attr) intRange else
|
||||
IntRange(intRange.first, intRange.last.coerceAtMost(cond.value))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += recurse(ruleMap, currRanges, cond.follow)
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val (_, ruleMap) = createMap(input)
|
||||
val ranges = listOf(IntRange(1, 4000), IntRange(1, 4000), IntRange(1, 4000), IntRange(1, 4000))
|
||||
return recurse(ruleMap, ranges, "in")
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day19_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 19114)
|
||||
check(testInputPart2Result == 167409079868000L)
|
||||
|
||||
val input = readInput("aoc2023/Day19")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package aoc2023
|
||||
|
||||
import lcm
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 20: Pulse Propagation ---
|
||||
https://adventofcode.com/2023/day/20
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
broadcaster -> a, b, c
|
||||
%a -> b
|
||||
%b -> c
|
||||
%c -> inv
|
||||
&inv -> a
|
||||
"""
|
||||
|
||||
val inlineTestInput2 = """
|
||||
broadcaster -> a
|
||||
%a -> inv, con
|
||||
&inv -> b
|
||||
%b -> con
|
||||
&con -> output
|
||||
"""
|
||||
|
||||
data class Node(
|
||||
val id: String,
|
||||
val targets: List<String>,
|
||||
val type: Int,
|
||||
val inputs: MutableList<String> = ArrayList(),
|
||||
var signalsIn: MutableList<Boolean> = ArrayList(),
|
||||
var stateIdx: Int = 0
|
||||
)
|
||||
|
||||
data class CacheInfo(val lowPulses: Long, val highPulses: Long, val nextState: BooleanArray)
|
||||
|
||||
fun createGraph(input: List<String>): HashMap<String, Node> {
|
||||
val nodes = HashMap<String, Node>()
|
||||
for (i in input) {
|
||||
val (source, targets) = i.split(" -> ")
|
||||
val targetList = targets.split(", ")
|
||||
val (id, type) = if (source.startsWith("%")) {
|
||||
source.removePrefix("%") to 1
|
||||
} else if (source.startsWith("&")) {
|
||||
source.removePrefix("&") to 2
|
||||
} else {
|
||||
source to 0
|
||||
}
|
||||
nodes[id] = Node(id, targetList, type)
|
||||
}
|
||||
var stateIdx = 0
|
||||
for (node in nodes.values) {
|
||||
node.stateIdx = stateIdx++
|
||||
for (t in node.targets) {
|
||||
nodes[t]?.inputs?.add(node.id)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val nodes = createGraph(input)
|
||||
val state = BooleanArray(nodes.size)
|
||||
val cache = HashMap<String, CacheInfo>()
|
||||
var totalLowPulses = 0L
|
||||
var totalHighPulses = 0L
|
||||
for (n in 1..1000) {
|
||||
val stateString = state.joinToString("") { if (it) "1" else "0" }
|
||||
val cached = cache[stateString]
|
||||
if (cached != null) {
|
||||
totalLowPulses += cached.lowPulses
|
||||
totalHighPulses += cached.highPulses
|
||||
cached.nextState.copyInto(state)
|
||||
continue
|
||||
}
|
||||
val queue = LinkedHashSet<String>()
|
||||
queue.add("broadcaster")
|
||||
nodes["broadcaster"]!!.signalsIn.add(false)
|
||||
var lowPulses = 1L
|
||||
var highPulses = 0L
|
||||
do {
|
||||
val id = queue.first()
|
||||
queue.remove(id)
|
||||
val node = nodes[id]
|
||||
if (node != null) {
|
||||
val signalsIn = node.signalsIn
|
||||
for (signalIn in signalsIn) {
|
||||
when (node.type) {
|
||||
0 -> {
|
||||
state[node.stateIdx] = signalIn
|
||||
if (state[node.stateIdx]) highPulses += node.targets.size else lowPulses += node.targets.size
|
||||
node.targets.forEach { nodes[it]?.signalsIn?.add(state[node.stateIdx]) }
|
||||
queue.addAll(node.targets)
|
||||
}
|
||||
|
||||
1 -> if (!signalIn) {
|
||||
state[node.stateIdx] = !state[node.stateIdx]
|
||||
if (state[node.stateIdx]) highPulses += node.targets.size else lowPulses += node.targets.size
|
||||
node.targets.forEach { nodes[it]?.signalsIn?.add(state[node.stateIdx]) }
|
||||
queue.addAll(node.targets)
|
||||
}
|
||||
|
||||
2 -> {
|
||||
state[node.stateIdx] = !node.inputs.all { state[nodes[it]!!.stateIdx] }
|
||||
if (state[node.stateIdx]) highPulses += node.targets.size else lowPulses += node.targets.size
|
||||
node.targets.forEach { nodes[it]?.signalsIn?.add(state[node.stateIdx]) }
|
||||
queue.addAll(node.targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
node.signalsIn.clear()
|
||||
}
|
||||
} while (queue.isNotEmpty())
|
||||
cache[stateString] = CacheInfo(lowPulses, highPulses, state.copyOf())
|
||||
totalLowPulses += lowPulses
|
||||
totalHighPulses += highPulses
|
||||
}
|
||||
return totalLowPulses * totalHighPulses
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val nodes = createGraph(input)
|
||||
val state = BooleanArray(nodes.size)
|
||||
var buttonCount = 0L
|
||||
val klNode = nodes.values.first { it.targets.contains("rx") }
|
||||
val periods = LongArray(klNode.inputs.size)
|
||||
do {
|
||||
buttonCount++
|
||||
val queue = LinkedHashSet<String>()
|
||||
queue.add("broadcaster")
|
||||
nodes["broadcaster"]!!.signalsIn.add(false)
|
||||
do {
|
||||
val id = queue.first()
|
||||
queue.remove(id)
|
||||
val node = nodes[id]
|
||||
if (node != null) {
|
||||
val signalsIn = node.signalsIn
|
||||
for (signalIn in signalsIn) {
|
||||
when (node.type) {
|
||||
0 -> {
|
||||
state[node.stateIdx] = signalIn
|
||||
node.targets.forEach { nodes[it]?.signalsIn?.add(state[node.stateIdx]) }
|
||||
queue.addAll(node.targets)
|
||||
}
|
||||
|
||||
1 -> if (!signalIn) {
|
||||
state[node.stateIdx] = !state[node.stateIdx]
|
||||
node.targets.forEach { nodes[it]?.signalsIn?.add(state[node.stateIdx]) }
|
||||
queue.addAll(node.targets)
|
||||
}
|
||||
|
||||
2 -> {
|
||||
state[node.stateIdx] = !node.inputs.all { state[nodes[it]!!.stateIdx] }
|
||||
node.targets.forEach { nodes[it]?.signalsIn?.add(state[node.stateIdx]) }
|
||||
queue.addAll(node.targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
node.signalsIn.clear()
|
||||
klNode.inputs.forEachIndexed { index, s ->
|
||||
if (state[nodes[s]!!.stateIdx]) {
|
||||
if (periods[index] == 0L) {
|
||||
periods[index] = buttonCount
|
||||
println("$index $s $buttonCount")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//node.targets.forEach { println("$rxCount: $lowPulses, $highPulses ${node.id} -${state[node.stateIdx]}> $it") }
|
||||
}
|
||||
} while (queue.isNotEmpty())
|
||||
//println("$buttonCount")
|
||||
} while (periods.any { it == 0L })
|
||||
return periods.asIterable().lcm()
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day20_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart1bResult = part1(testInput2)
|
||||
println("Part 1b Test: $testInputPart1bResult")
|
||||
check(testInputPart1Result == 32000000L)
|
||||
check(testInputPart1bResult == 11687500L)
|
||||
|
||||
val input = readInput("aoc2023/Day20")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 21: Step Counter ---
|
||||
https://adventofcode.com/2023/day/21
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
...........
|
||||
.....###.#.
|
||||
.###.##..#.
|
||||
..#.#...#..
|
||||
....#.#....
|
||||
.##..S####.
|
||||
.##..#...#.
|
||||
.......##..
|
||||
.##.#.####.
|
||||
.##..##.##.
|
||||
...........
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>, steps: Int): Int {
|
||||
var grid = CharGrid(input, '#')
|
||||
for (i in 1..steps) {
|
||||
grid = grid.grow(CharGrid.PLUS_POS, { it == 'S' }, { org: Char, old: Char, _: RelPos ->
|
||||
if (old != '#') 'O' else old
|
||||
})
|
||||
grid.apply {
|
||||
when (it) {
|
||||
'S' -> '.'
|
||||
'O' -> 'S'
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
}
|
||||
return grid.findMatches { it == 'S' }.count()
|
||||
}
|
||||
|
||||
fun fn(x: Long) = 61124L * x * x - 91368L * x + 34140L
|
||||
|
||||
fun part2(input: List<String>, steps: Int): Long {
|
||||
val grid = CharGrid(input, '%')
|
||||
var exp = grid.findMatches { it == 'S' }.map { RelPos(it.first, it.second) }.toSet()
|
||||
val mp = steps % 262
|
||||
for (i in 1..(262 * 2 + mp)) {
|
||||
exp =
|
||||
exp.flatMap {
|
||||
CharGrid.PLUS_POS.map { rp -> it.translate(rp) }
|
||||
.filter { grid[Math.floorMod(it.dr, grid.width), Math.floorMod(it.dc, grid.height)] != '#' }
|
||||
}.toSet()
|
||||
val count = exp.size
|
||||
if (i % 262 == mp) println("$i: $count")
|
||||
}
|
||||
println(fn(1L))
|
||||
println(fn(2L))
|
||||
println(fn(3L))
|
||||
|
||||
// Wolfram alpha says: {3896, 95900, 310152} -> 61124 x^2 - 91368 x + 34140
|
||||
return fn((steps / 262L) + 1)
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day21_test")
|
||||
val testInputPart1Result = part1(testInput, 6)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
//val testInputPart2Result = part2(testInput, 500)
|
||||
//println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 16)
|
||||
//check(testInputPart2Result == 167004L)
|
||||
|
||||
val input = readInput("aoc2023/Day21")
|
||||
part1(input, 64).println()
|
||||
part2(input, 26501365).println()
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 22: Sand Slabs ---
|
||||
https://adventofcode.com/2023/day/22
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
1,0,1~1,2,1
|
||||
0,0,2~2,0,2
|
||||
0,2,3~2,2,3
|
||||
0,0,4~0,2,4
|
||||
2,0,5~2,2,5
|
||||
0,1,6~2,1,6
|
||||
1,1,8~1,1,9
|
||||
"""
|
||||
|
||||
data class Brick(
|
||||
val id: Int,
|
||||
val x1: Int,
|
||||
val y1: Int,
|
||||
val z1: Int,
|
||||
val x2: Int,
|
||||
val y2: Int,
|
||||
val z2: Int,
|
||||
val onTopBricks: MutableSet<Brick> = HashSet(),
|
||||
val uponBricks: MutableSet<Brick> = HashSet()
|
||||
) {
|
||||
fun h() = z2 - z1 + 1
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Brick
|
||||
|
||||
return id == other.id
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Brick(id=$id, uponBricks=$uponBricks)"
|
||||
}
|
||||
}
|
||||
|
||||
fun parts(input: List<String>, width: Int, height: Int): Pair<Int, Int> {
|
||||
val bricks = input.mapIndexed { id, it ->
|
||||
val (x1, y1, z1, x2, y2, z2) = "(\\d+),(\\d+),(\\d+)~(\\d+),(\\d+),(\\d+)".toRegex()
|
||||
.matchEntire(it)?.destructured!!
|
||||
Brick(id, x1.toInt(), y1.toInt(), z1.toInt(), x2.toInt(), y2.toInt(), z2.toInt())
|
||||
}
|
||||
.sortedBy { it.z2 }
|
||||
val brickTopZ = IntArray(width * height)
|
||||
val brickStacks = Array<MutableList<Brick>>(width * height) { ArrayList() }
|
||||
for (b in bricks) {
|
||||
var flatTopZ = 0
|
||||
for (y in b.y1..b.y2) {
|
||||
for (x in b.x1..b.x2) {
|
||||
flatTopZ = flatTopZ.coerceAtLeast(brickTopZ[y * width + x])
|
||||
}
|
||||
}
|
||||
for (y in b.y1..b.y2) {
|
||||
for (x in b.x1..b.x2) {
|
||||
val topBrick = brickStacks[y * width + x].lastOrNull()
|
||||
if (topBrick != null) {
|
||||
val topZ = brickTopZ[y * width + x]
|
||||
if (topZ == flatTopZ) {
|
||||
b.uponBricks.add(topBrick)
|
||||
topBrick.onTopBricks.add(b)
|
||||
}
|
||||
}
|
||||
brickStacks[y * width + x].add(b)
|
||||
brickTopZ[y * width + x] = flatTopZ + b.h()
|
||||
}
|
||||
}
|
||||
}
|
||||
val disBricks = bricks.filterNot { it.onTopBricks.all { it.uponBricks.size != 1 } }
|
||||
val safeBricks = bricks.size - disBricks.size
|
||||
var fallingSum = 0
|
||||
for (brick in disBricks) {
|
||||
val fallingSet = HashSet<Brick>()
|
||||
val removeQueue = ArrayDeque<Brick>()
|
||||
fallingSet.add(brick)
|
||||
removeQueue.add(brick)
|
||||
while (removeQueue.isNotEmpty()) {
|
||||
val falling = removeQueue.removeFirst()
|
||||
fallingSet.add(falling)
|
||||
removeQueue.addAll(falling.onTopBricks.filter { fallingSet.containsAll(it.uponBricks) })
|
||||
}
|
||||
fallingSum += fallingSet.size - 1
|
||||
}
|
||||
return safeBricks to fallingSum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day22_test")
|
||||
val (testInputPart1Result, testInputPart2Result) = parts(testInput, 3, 3)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 5)
|
||||
check(testInputPart2Result == 7)
|
||||
|
||||
val input = readInput("aoc2023/Day22")
|
||||
parts(input, 10, 10).println()
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package aoc2023
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 23: A Long Walk ---
|
||||
https://adventofcode.com/2023/day/23
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
#.#####################
|
||||
#.......#########...###
|
||||
#######.#########.#.###
|
||||
###.....#.>.>.###.#.###
|
||||
###v#####.#v#.###.#.###
|
||||
###.>...#.#.#.....#...#
|
||||
###v###.#.#.#########.#
|
||||
###...#.#.#.......#...#
|
||||
#####.#.#.#######.#.###
|
||||
#.....#.#.#.......#...#
|
||||
#.#####.#.#.#########v#
|
||||
#.#...#...#...###...>.#
|
||||
#.#.#v#######v###.###v#
|
||||
#...#.>.#...>.>.#.###.#
|
||||
#####v#.#.###v#.#.###.#
|
||||
#.....#...#...#.#.#...#
|
||||
#.#########.###.#.#.###
|
||||
#...###...#...#...#.###
|
||||
###.###.#.###v#####v###
|
||||
#...#...#.#.>.>.#.>.###
|
||||
#.###.###.#.###.#.#v###
|
||||
#.....###...###...#...#
|
||||
#####################.#
|
||||
"""
|
||||
|
||||
data class Node(val pos: RelPos, val relLen: Int, val path: LinkedHashSet<RelPos>)
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input, '#')
|
||||
val queue = ArrayDeque<Node>()
|
||||
queue.add(Node(RelPos(1, 0), 0, LinkedHashSet()))
|
||||
var maxLength = 0
|
||||
while (queue.isNotEmpty()) {
|
||||
val node = queue.removeFirst()
|
||||
var pos = node.pos
|
||||
var relLen = node.relLen
|
||||
val path = node.path
|
||||
path.add(pos)
|
||||
var lastPos = pos
|
||||
do {
|
||||
if (pos.dr == grid.height - 1) {
|
||||
maxLength = maxLength.coerceAtLeast(relLen)
|
||||
break
|
||||
}
|
||||
relLen++
|
||||
var nextPos = CharGrid.PLUS_POS
|
||||
.filter { grid[pos.dc + it.dc, pos.dr + it.dr] == '.' && (pos.dc + it.dc != lastPos.dc || pos.dr + it.dr != lastPos.dr) }
|
||||
.map { it.translate(pos) }.singleOrNull { !path.contains(it) }
|
||||
val branchPos = CharGrid.PLUS_POS
|
||||
.filter {
|
||||
val c = grid[pos.dc + it.dc, pos.dr + it.dr]
|
||||
(c == '>' && it.dc > 0) || (c == 'v' && it.dr > 0)
|
||||
|| c == '?'
|
||||
}
|
||||
.map { it.translate(pos) }.filter { !path.contains(it) }
|
||||
|
||||
if (nextPos == null && branchPos.size == 1) {
|
||||
nextPos = branchPos.single()
|
||||
} else {
|
||||
if (branchPos.isNotEmpty()) {
|
||||
path.add(pos)
|
||||
branchPos.forEach { queue.addLast(Node(it, relLen, LinkedHashSet(path))) }
|
||||
}
|
||||
}
|
||||
if (nextPos != null) {
|
||||
lastPos = pos
|
||||
pos = nextPos
|
||||
}
|
||||
} while (nextPos != null)
|
||||
}
|
||||
|
||||
return maxLength
|
||||
}
|
||||
|
||||
data class GNode(val pos: RelPos, val edges: MutableSet<Pair<Int, GNode>> = HashSet()) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as GNode
|
||||
|
||||
return pos == other.pos
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return pos.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "GNode(pos=$pos)"
|
||||
}
|
||||
}
|
||||
|
||||
fun rec(endnode: GNode, node: GNode, len: Int, visited: Array<BooleanArray>): Int {
|
||||
visited[node.pos.dr][node.pos.dc] = true
|
||||
var maxLen = 0
|
||||
for (e in node.edges) {
|
||||
if (e.second == endnode) {
|
||||
visited[node.pos.dr][node.pos.dc] = false
|
||||
return len + e.first
|
||||
}
|
||||
if (!visited[e.second.pos.dr][e.second.pos.dc]) {
|
||||
val longestPath = rec(endnode, e.second, len + e.first, visited)
|
||||
maxLen = maxLen.coerceAtLeast(longestPath)
|
||||
}
|
||||
}
|
||||
visited[node.pos.dr][node.pos.dc] = false
|
||||
return maxLen
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val modinput = input.map { it.replace(">", ".").replace("v", ".") }.toList()
|
||||
|
||||
val map = HashMap<RelPos, GNode>()
|
||||
val grid = CharGrid(modinput, '#')
|
||||
val queue = ArrayDeque<Pair<GNode, RelPos>>()
|
||||
val startNode = GNode(RelPos(1, 0))
|
||||
queue.add(startNode to RelPos(0, 1))
|
||||
map[startNode.pos] = startNode
|
||||
|
||||
val endPos = RelPos(grid.width - 2, grid.height - 1)
|
||||
val endNode = GNode(endPos)
|
||||
map[endPos] = endNode
|
||||
val visited = HashSet<GNode>()
|
||||
visited.add(startNode)
|
||||
while (queue.isNotEmpty()) {
|
||||
val (node, dir) = queue.removeFirst()
|
||||
var pos = node.pos.translate(dir)
|
||||
var relLen = 0
|
||||
var lastPos = node.pos
|
||||
do {
|
||||
val nextDirs = CharGrid.PLUS_POS
|
||||
.filter { grid[pos.dc + it.dc, pos.dr + it.dr] == '.' && (pos.dc + it.dc != lastPos.dc || pos.dr + it.dr != lastPos.dr) }
|
||||
|
||||
relLen++
|
||||
if (nextDirs.size == 1) {
|
||||
lastPos = pos
|
||||
pos = pos.translate(nextDirs.single())
|
||||
if (pos == endPos) {
|
||||
node.edges.add(relLen + 1 to endNode)
|
||||
endNode.edges.add(relLen + 1 to node)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
val branchNode = map.computeIfAbsent(pos) { GNode(it) }
|
||||
branchNode.edges.add(relLen to node)
|
||||
node.edges.add(relLen to branchNode)
|
||||
if (!visited.contains(branchNode)) {
|
||||
visited.add(branchNode)
|
||||
nextDirs.forEach { queue.addLast(branchNode to it) }
|
||||
}
|
||||
}
|
||||
} while (nextDirs.size == 1)
|
||||
}
|
||||
|
||||
// optimize endnode path
|
||||
var cNode: GNode = endNode
|
||||
var pNode = cNode
|
||||
while (cNode.edges.size == 1) {
|
||||
pNode = cNode
|
||||
cNode = cNode.edges.single().second
|
||||
}
|
||||
cNode.edges.removeIf { it.second != pNode }
|
||||
|
||||
return rec(endNode, startNode, 0, Array(grid.height) { BooleanArray(grid.width) })
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day23_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 94)
|
||||
check(testInputPart2Result == 154)
|
||||
|
||||
val input = readInput("aoc2023/Day23")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package aoc2023
|
||||
|
||||
import chineseRemainder
|
||||
import primeFactors
|
||||
import println
|
||||
import readInput
|
||||
import sieveOfErastosthenes
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sign
|
||||
|
||||
/*
|
||||
--- Day 24: Never Tell Me The Odds ---
|
||||
https://adventofcode.com/2023/day/24
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
19, 13, 30 @ -2, 1, -2
|
||||
18, 19, 22 @ -1, -1, -2
|
||||
20, 25, 34 @ -2, -2, -4
|
||||
12, 31, 28 @ -1, -2, -1
|
||||
20, 19, 15 @ 1, -5, -3
|
||||
"""
|
||||
|
||||
data class Hailstone(val px: Long, val py: Long, val pz: Long, val vx: Int, val vy: Int, val vz: Int)
|
||||
|
||||
fun part1(input: List<String>, testx: LongRange, testy: LongRange): Int {
|
||||
val hailstones = input.map {
|
||||
val (x1, y1, z1, x2, y2, z2) = "(\\d+), (\\d+), (\\d+) @ ([-\\d]+), ([-\\d]+), ([-\\d]+)".toRegex()
|
||||
.matchEntire(it)?.destructured!!
|
||||
Hailstone(x1.toLong(), y1.toLong(), z1.toLong(), x2.toInt(), y2.toInt(), z2.toInt())
|
||||
}
|
||||
var count = 0
|
||||
for (h1 in hailstones.indices) {
|
||||
for (h2 in h1 + 1..hailstones.lastIndex) {
|
||||
val s1 = hailstones[h1]
|
||||
val s2 = hailstones[h2]
|
||||
|
||||
val dx = (s2.px - s1.px).toDouble()
|
||||
val dy = (s2.py - s1.py).toDouble()
|
||||
|
||||
val d = s1.vx.toDouble() * s2.vy.toDouble() -
|
||||
s1.vy.toDouble() * s2.vx.toDouble()
|
||||
|
||||
val t = dx * s2.vy.toDouble() - dy * s2.vx.toDouble()
|
||||
val u = dx * s1.vy.toDouble() - dy * s1.vx.toDouble()
|
||||
|
||||
if (t * d.sign > 0 && u * d.sign > 0) {
|
||||
val hx = (s1.px + (t * s1.vx.toDouble()) / d)
|
||||
val hy = (s1.py + (t * s1.vy.toDouble()) / d)
|
||||
if (hx.toLong() in testx && hy.toLong() in testy) count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val hailstones = input.map {
|
||||
val (x1, y1, z1, x2, y2, z2) = "(\\d+), (\\d+), (\\d+) @ ([-\\d]+), ([-\\d]+), ([-\\d]+)".toRegex()
|
||||
.matchEntire(it)?.destructured!!
|
||||
Hailstone(x1.toLong(), y1.toLong(), z1.toLong(), x2.toInt(), y2.toInt(), z2.toInt())
|
||||
}//.sortedBy { abs(it.vx) + abs(it.vy) + abs(it.vz) }
|
||||
//.take(3)
|
||||
|
||||
val h1 = hailstones[0]
|
||||
val h2 = hailstones[1]
|
||||
val h3 = hailstones[2]
|
||||
|
||||
// rx = h1.px + t1*(h1.vx - rvx) = h2.px + t2*(h2.vx - rvx) = h3.px + t3*(h3.vx - rvx)
|
||||
// ry = h1.py + t1*(h1.vy - rvy) = h2.py + t2*(h2.vy - rvy) = h3.py + t3*(h3.vy - rvy)
|
||||
// rz = h1.pz + t1*(h1.vz - rvz) = h2.pz + t2*(h2.vz - rvz) = h3.pz + t3*(h3.vz - rvz)
|
||||
|
||||
// [1] h1.px - h2.px = t2*(h2.vx - rvx) - t1*(h1.vx - rvx)
|
||||
// [2] h1.px - h3.px = t3*(h3.vx - rvx) - t1*(h1.vx - rvx)
|
||||
// [3] h2.px - h3.px = t3*(h3.vx - rvx) - t2*(h2.vx - rvx)
|
||||
|
||||
// [4] h1.py - h2.py = t2*(h2.vy - rvy) - t1*(h1.vy - rvy)
|
||||
// [5] h1.py - h3.py = t3*(h3.vy - rvy) - t1*(h1.vy - rvy)
|
||||
// [6] h2.py - h3.py = t3*(h3.vy - rvy) - t2*(h2.vy - rvy)
|
||||
|
||||
// [7] h1.pz - h2.pz = t2*(h2.vz - rvz) - t1*(h1.vz - rvz)
|
||||
// [8] h1.pz - h3.pz = t3*(h3.vz - rvz) - t1*(h1.vz - rvz)
|
||||
// [9] h2.pz - h3.pz = t3*(h3.vz - rvz) - t2*(h2.vz - rvz)
|
||||
|
||||
val sieve = sieveOfErastosthenes(200)
|
||||
for (rvx in -1000..1000) {
|
||||
val speedMods = hailstones.mapNotNull { if (it.vx - rvx != 0) abs(it.vx - rvx).toLong() to (it.px % abs(it.vx - rvx)) else null }
|
||||
|
||||
val factorMap = HashMap<Long, Long>()
|
||||
var good = true
|
||||
reducePairs@ for ((speed, remain) in speedMods) {
|
||||
for (pf in primeFactors(speed, sieve)) {
|
||||
val sf = factorMap[pf]
|
||||
val pr = remain % pf
|
||||
if (sf != null && sf != pr) {
|
||||
good = false
|
||||
break@reducePairs
|
||||
}
|
||||
factorMap[pf] = pr
|
||||
}
|
||||
}
|
||||
if (!good) continue
|
||||
val reducedPairs = factorMap.map { it.value to it.key }.sortedBy { it.second }
|
||||
|
||||
val rx = reducedPairs.chineseRemainder()
|
||||
//val rx = 24
|
||||
|
||||
val bang1Time = (h1.px - rx) / (rvx - h1.vx)
|
||||
val bang2Time = (h2.px - rx) / (rvx - h2.vx)
|
||||
val h1px = h1.px + h1.vx * bang1Time
|
||||
val h1py = h1.py + h1.vy * bang1Time
|
||||
val h1pz = h1.pz + h1.vz * bang1Time
|
||||
val h2px = h2.px + h2.vx * bang2Time
|
||||
val h2py = h2.py + h2.vy * bang2Time
|
||||
val h2pz = h2.pz + h2.vz * bang2Time
|
||||
val rvy = (h2py - h1py) / (bang2Time - bang1Time)
|
||||
val rvz = (h2pz - h1pz) / (bang2Time - bang1Time)
|
||||
val ry = h1py - bang1Time * rvy
|
||||
val rz = h1pz - bang1Time * rvz
|
||||
return rx + ry + rz
|
||||
}
|
||||
return 0L
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day24_test")
|
||||
val testInputPart1Result = part1(testInput, 7L..27L, 7L..27L)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 2)
|
||||
//check(testInputPart2Result == 47L)
|
||||
|
||||
val input = readInput("aoc2023/Day24")
|
||||
part1(input, 200000000000000L..400000000000000L, 200000000000000L..400000000000000L).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package aoc2023
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 25: Snowverload ---
|
||||
https://adventofcode.com/2023/day/25
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
jqt: rhn xhk nvd
|
||||
rsh: frs pzl lsr
|
||||
xhk: hfx
|
||||
cmg: qnr nvd lhk bvb
|
||||
rhn: xhk bvb hfx
|
||||
bvb: xhk hfx
|
||||
pzl: lsr hfx nvd
|
||||
qnr: nvd
|
||||
ntq: jqt hfx bvb xhk
|
||||
nvd: lhk
|
||||
lsr: lhk
|
||||
rzs: qnr cmg lsr rsh
|
||||
frs: qnr lhk lsr
|
||||
"""
|
||||
|
||||
data class Node(val id: String, val children: MutableSet<Node> = HashSet(), val orgNodes: MutableSet<Node> = HashSet()) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Node
|
||||
|
||||
return id == other.id
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Node(id='$id', children=${children.joinToString { it.id }})"
|
||||
}
|
||||
}
|
||||
|
||||
fun connectedSize(rootNode: Node): Int {
|
||||
val visited = HashSet<Node>()
|
||||
val queue = ArrayDeque<Node>()
|
||||
queue.add(rootNode)
|
||||
while (queue.isNotEmpty()) {
|
||||
val n = queue.removeFirst()
|
||||
visited.add(n)
|
||||
n.children.forEach { if (!visited.contains(it)) queue.add(it) }
|
||||
}
|
||||
return visited.size
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val nodeMap = HashMap<String, Node>()
|
||||
for (i in input) {
|
||||
val (from, to) = i.split(": ")
|
||||
val tos = to.split(" ")
|
||||
val fromNode = nodeMap.computeIfAbsent(from) { Node(from) }
|
||||
val toNodes = tos.map { nodeMap.computeIfAbsent(it) { Node(it) } }
|
||||
fromNode.children.addAll(toNodes)
|
||||
toNodes.forEach { it.children.add(fromNode) }
|
||||
}
|
||||
|
||||
nodeMap["vps"]!!.children.remove(nodeMap["pzc"])
|
||||
nodeMap["pzc"]!!.children.remove(nodeMap["vps"])
|
||||
nodeMap["xvk"]!!.children.remove(nodeMap["sgc"])
|
||||
nodeMap["sgc"]!!.children.remove(nodeMap["xvk"])
|
||||
nodeMap["dph"]!!.children.remove(nodeMap["cvx"])
|
||||
nodeMap["cvx"]!!.children.remove(nodeMap["dph"])
|
||||
|
||||
val res = connectedSize(nodeMap["vps"]!!) * connectedSize(nodeMap["pzc"]!!)
|
||||
|
||||
// val edges = nodeMap.values.map { s -> s.children.map { t -> s to t } }.flatten()
|
||||
//
|
||||
// val nodes = nodeMap.values.toMutableList()
|
||||
// while (nodes.size > 2) {
|
||||
// val n1 = nodes[(Math.random() * nodes.size).toInt().coerceAtMost(nodes.lastIndex)]
|
||||
// val n2 = n1.children.keys.toList()[(Math.random() * n1.children.size).toInt().coerceAtMost(n1.children.size - 1)]
|
||||
// val superNode = Node(n1.id + "-" + n2.id)
|
||||
// if (n1.orgNodes.isEmpty()) {
|
||||
// superNode.orgNodes.add(Node(n1.id, children = HashMap(n1.children)))
|
||||
// } else {
|
||||
// superNode.orgNodes.addAll(n1.orgNodes)
|
||||
// }
|
||||
// if (n2.orgNodes.isEmpty()) {
|
||||
// superNode.orgNodes.add(Node(n2.id, children = HashMap(n2.children)))
|
||||
// } else {
|
||||
// superNode.orgNodes.addAll(n2.orgNodes)
|
||||
// }
|
||||
// n1.children.filter { it.key != n2 }
|
||||
// .forEach { }
|
||||
// superNode.children.addAll(n1.children.filter { it != n2 })
|
||||
// superNode.children.addAll(n2.children.filter { it != n1 })
|
||||
//
|
||||
// superNode.children.forEach {
|
||||
// it.children.remove(n1)
|
||||
// it.children.remove(n2)
|
||||
// it.children.add(superNode)
|
||||
// }
|
||||
// nodes.remove(n1)
|
||||
// nodes.remove(n2)
|
||||
// nodes.add(superNode)
|
||||
// }
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2023/Day25_test")
|
||||
//val testInputPart1Result = part1(testInput)
|
||||
//println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
//check(testInputPart1Result == 0)
|
||||
check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2023/Day25")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,61 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
import kotlin.math.abs
|
||||
|
||||
/*
|
||||
--- Day 1: Historian Hysteria ---
|
||||
https://adventofcode.com/2024/day/1
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
3 4
|
||||
4 3
|
||||
2 5
|
||||
1 3
|
||||
3 9
|
||||
3 3
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val left = ArrayList<Int>()
|
||||
val right = ArrayList<Int>()
|
||||
for (i in input) {
|
||||
val (l, r) = i.splitInts()
|
||||
left.add(l)
|
||||
right.add(r)
|
||||
}
|
||||
left.sort()
|
||||
right.sort()
|
||||
return left.zip(right).sumOf { abs(it.first - it.second) }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val left = ArrayList<Int>()
|
||||
val right = ArrayList<Int>()
|
||||
for (i in input) {
|
||||
val (l, r) = i.splitInts()
|
||||
left.add(l)
|
||||
right.add(r)
|
||||
}
|
||||
val grouped = right.groupBy { it }
|
||||
return left.sumOf { it * (grouped[it]?.size ?: 0) }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day01")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 11)
|
||||
check(testInputPart2Result == 31)
|
||||
|
||||
val input = readInput("aoc2024/Day01")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
|
||||
/*
|
||||
--- Day 2: Red-Nosed Reports ---
|
||||
https://adventofcode.com/2024/day/2
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
7 6 4 2 1
|
||||
1 2 7 8 9
|
||||
9 7 6 2 1
|
||||
1 3 2 4 5
|
||||
8 6 4 4 1
|
||||
1 3 6 7 9
|
||||
"""
|
||||
|
||||
fun checkGood(i: List<Int>): Boolean {
|
||||
val dir = i[1] - i[0]
|
||||
return if (dir < 0) {
|
||||
!i.runningReduce { acc, elem -> if ((elem < acc) and (elem >= acc - 3)) elem else Int.MIN_VALUE }
|
||||
.contains(Int.MIN_VALUE)
|
||||
} else if (dir > 0) {
|
||||
!i.runningReduce { acc, elem -> if ((elem > acc) && (elem <= acc + 3)) elem else Int.MAX_VALUE }
|
||||
.contains(Int.MAX_VALUE)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
return input.map { it.splitInts() }.count(::checkGood)
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val ilist = input.map { it.splitInts() }
|
||||
var sum = 0
|
||||
for (i in ilist) {
|
||||
for (dp in ilist.indices) {
|
||||
if (checkGood(i.filterIndexed { index, _ -> dp != index })) {
|
||||
sum++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day02_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 2)
|
||||
check(testInputPart2Result == 4)
|
||||
|
||||
val input = readInput("aoc2024/Day02")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 3: Mull It Over ---
|
||||
https://adventofcode.com/2024/day/3
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))
|
||||
"""
|
||||
val inlineTestInput2 = """
|
||||
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)don't()mul(10,10)
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
return "mul\\((\\d+),(\\d+)\\)".toRegex().findAll(input.joinToString(""))
|
||||
.sumOf { it.groupValues[1].toInt() * it.groupValues[2].toInt() }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
return "mul\\((\\d+),(\\d+)\\)".toRegex()
|
||||
.findAll((input.joinToString("") + "do()").replace("(don't\\(\\).*?)+(do\\(\\)|$)".toRegex(), ""))
|
||||
.sumOf { it.groupValues[1].toInt() * it.groupValues[2].toInt() }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day03_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput2)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 161)
|
||||
check(testInputPart2Result == 48)
|
||||
|
||||
val input = readInput("aoc2024/Day03")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 4: Ceres Search ---
|
||||
https://adventofcode.com/2024/day/4
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
MMMSXXMASM
|
||||
MSAMXMSMSA
|
||||
AMXSXMAAMM
|
||||
MSAMASMSMX
|
||||
XMASAMXAMM
|
||||
XXAMMXXAMA
|
||||
SMSMSASXSS
|
||||
SAXAMASAAA
|
||||
MAMMMXMMMM
|
||||
MXMXAXMASX
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val relposes = listOf(
|
||||
listOf(RelPos(0, 0), RelPos(1, 0), RelPos(2, 0), RelPos(3, 0)),
|
||||
listOf(RelPos(0, 0), RelPos(-1, 0), RelPos(-2, 0), RelPos(-3, 0)),
|
||||
listOf(RelPos(0, 0), RelPos(1, 1), RelPos(2, 2), RelPos(3, 3)),
|
||||
listOf(RelPos(0, 0), RelPos(-1, -1), RelPos(-2, -2), RelPos(-3, -3)),
|
||||
listOf(RelPos(0, 0), RelPos(-1, 1), RelPos(-2, 2), RelPos(-3, 3)),
|
||||
listOf(RelPos(0, 0), RelPos(1, -1), RelPos(2, -2), RelPos(3, -3)),
|
||||
listOf(RelPos(0, 0), RelPos(0, 1), RelPos(0, 2), RelPos(0, 3)),
|
||||
listOf(RelPos(0, 0), RelPos(0, -1), RelPos(0, -2), RelPos(0, -3)),
|
||||
)
|
||||
return grid.countStrings(relposes, "XMAS")
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val relposes = listOf(
|
||||
listOf(RelPos(-1, -1), RelPos(0, 0), RelPos(1, 1)),
|
||||
listOf(RelPos(-1, 1), RelPos(0, 0), RelPos(1, -1)),
|
||||
)
|
||||
return grid.findCombinedMatches(relposes) { it.all { it == "MAS" || it == "SAM" } }.count()
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day04_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 18)
|
||||
check(testInputPart2Result == 9)
|
||||
|
||||
val input = readInput("aoc2024/Day04")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 5: Print Queue ---
|
||||
https://adventofcode.com/2024/day/5
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
47|53
|
||||
97|13
|
||||
97|61
|
||||
97|47
|
||||
75|29
|
||||
61|13
|
||||
75|53
|
||||
29|13
|
||||
97|29
|
||||
53|29
|
||||
61|53
|
||||
97|53
|
||||
61|29
|
||||
47|13
|
||||
75|47
|
||||
97|75
|
||||
47|61
|
||||
75|61
|
||||
47|29
|
||||
75|13
|
||||
53|13
|
||||
|
||||
75,47,61,53,29
|
||||
97,61,53,29,13
|
||||
75,29,13
|
||||
75,97,47,61,53
|
||||
61,13,29
|
||||
97,13,75,29,47
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val beforeList = Array(100) { HashSet<Int>() }
|
||||
var idx = 0
|
||||
while (input[idx].isNotEmpty()) {
|
||||
val (page, min) = input[idx++].split('|').map(String::toInt)
|
||||
beforeList[page].add(min)
|
||||
}
|
||||
idx++
|
||||
var sum = 0
|
||||
for (i in idx until input.size) {
|
||||
val pages = input[i].split(',').map(String::toInt)
|
||||
val visited = HashSet<Int>()
|
||||
var inOrder = true
|
||||
for (p in pages) {
|
||||
val good = beforeList[p].none { visited.contains(it) }
|
||||
if (!good) {
|
||||
inOrder = false
|
||||
break
|
||||
}
|
||||
visited.add(p)
|
||||
}
|
||||
if (inOrder) {
|
||||
sum += pages[pages.size / 2]
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val beforeList = Array(100) { HashSet<Int>() }
|
||||
val afterList = Array(100) { HashSet<Int>() }
|
||||
var idx = 0
|
||||
while (input[idx].isNotEmpty()) {
|
||||
val (page, min) = input[idx++].split('|').map(String::toInt)
|
||||
beforeList[page].add(min)
|
||||
afterList[min].add(page)
|
||||
}
|
||||
idx++
|
||||
var sum = 0
|
||||
for (i in idx until input.size) {
|
||||
var fixedIt = false
|
||||
val pages = input[i].split(',').map(String::toInt).toMutableList()
|
||||
do {
|
||||
val visited = HashSet<Int>()
|
||||
var inOrder = true
|
||||
for ((pi, p) in pages.withIndex()) {
|
||||
val badOnes = beforeList[p].filter { visited.contains(it) }
|
||||
if (badOnes.isNotEmpty()) {
|
||||
fixedIt = true
|
||||
inOrder = false
|
||||
for (b in badOnes) {
|
||||
for (bi in pi - 1 downTo 0) {
|
||||
if (pages[bi] == b) {
|
||||
pages[bi] = pages[pi]
|
||||
pages[pi] = b
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visited.add(p)
|
||||
}
|
||||
} while (!inOrder)
|
||||
if (fixedIt) {
|
||||
sum += pages[pages.size / 2]
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day05_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 143)
|
||||
check(testInputPart2Result == 123)
|
||||
|
||||
val input = readInput("aoc2024/Day05")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 6: Guard Gallivant ---
|
||||
https://adventofcode.com/2024/day/6
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
....#.....
|
||||
.........#
|
||||
..........
|
||||
..#.......
|
||||
.......#..
|
||||
..........
|
||||
.#..^.....
|
||||
........#.
|
||||
#.........
|
||||
......#...
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input, '.')
|
||||
val (oc, or) = grid.findMatches { it == '^' }[0]
|
||||
|
||||
var pos = RelPos(oc, or)
|
||||
var dir = RelPos(0, -1)
|
||||
while (grid.isInside(pos)) {
|
||||
grid[pos] = 'X'
|
||||
while (grid[pos.translate(dir)] == '#') {
|
||||
dir = RelPos(-dir.dr, dir.dc)
|
||||
}
|
||||
pos = pos.translate(dir)
|
||||
}
|
||||
return grid.findMatches { it == 'X' }.count()
|
||||
}
|
||||
|
||||
fun checkLooping(grid: CharGrid, origPos: RelPos): Boolean {
|
||||
val dirgrid = Array(grid.height) { IntArray(grid.width) }
|
||||
var dir = RelPos(0, -1)
|
||||
var dirBit = 0
|
||||
var looping = false
|
||||
var pos = origPos
|
||||
while (grid.isInside(pos)) {
|
||||
if ((dirgrid[pos.dc][pos.dr] and (1 shl dirBit)) != 0) {
|
||||
looping = true
|
||||
break
|
||||
}
|
||||
dirgrid[pos.dc][pos.dr] = dirgrid[pos.dc][pos.dr] or (1 shl dirBit)
|
||||
while (grid[pos.translate(dir)] == '#') {
|
||||
dir = RelPos(-dir.dr, dir.dc)
|
||||
dirBit = (dirBit + 1) and 3
|
||||
}
|
||||
pos = pos.translate(dir)
|
||||
}
|
||||
return looping
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input, '.')
|
||||
val obstaclePos = HashSet<RelPos>()
|
||||
val solutions = HashSet<RelPos>()
|
||||
val (oc, or) = grid.findMatches { it == '^' }[0]
|
||||
|
||||
var pos = RelPos(oc, or)
|
||||
var dir = RelPos(0, -1)
|
||||
while (true) {
|
||||
grid[pos] = 'X'
|
||||
val newPos = pos.translate(dir)
|
||||
if (!grid.isInside(newPos)) break
|
||||
if (grid[newPos] == '#') {
|
||||
dir = RelPos(-dir.dr, dir.dc)
|
||||
} else {
|
||||
if (grid[newPos] == '.') {
|
||||
obstaclePos.add(newPos)
|
||||
}
|
||||
pos = newPos
|
||||
}
|
||||
}
|
||||
for (op in obstaclePos) {
|
||||
grid[op] = '#'
|
||||
if (checkLooping(grid, RelPos(oc, or))) {
|
||||
solutions.add(op)
|
||||
}
|
||||
grid[op] = '.'
|
||||
}
|
||||
return solutions.size
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day06_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 41)
|
||||
check(testInputPart2Result == 6)
|
||||
|
||||
val input = readInput("aoc2024/Day06")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitLongs
|
||||
|
||||
/*
|
||||
--- Day 7: Bridge Repair ---
|
||||
https://adventofcode.com/2024/day/7
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
190: 10 19
|
||||
3267: 81 40 27
|
||||
83: 17 5
|
||||
156: 15 6
|
||||
7290: 6 8 6 15
|
||||
161011: 16 10 13
|
||||
192: 17 8 14
|
||||
21037: 9 7 18 13
|
||||
292: 11 6 16 20
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
var sum = 0L
|
||||
for (i in input) {
|
||||
val numbers = i.splitLongs(" ", ":")
|
||||
val eqres = numbers[0]
|
||||
val ops = numbers.drop(1)
|
||||
val combs = (1 shl (ops.size - 1))
|
||||
for (t in 0 until combs) {
|
||||
val res = ops.reduceIndexed { index, acc, l -> if (((1 shl (index - 1)) and t) != 0) acc * l else acc + l }
|
||||
if (res == eqres) {
|
||||
sum += eqres
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
val powTenTable = generateSequence(1L) { it * 10L }.take(10).toList().toLongArray()
|
||||
val powThreeTable = generateSequence(1) { it * 3 }.take(12).toList().toIntArray()
|
||||
|
||||
fun fastCeilLog10(n: Long): Int {
|
||||
for (i in 1 until 9) {
|
||||
if (powTenTable[i] > n) return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
var sum = 0L
|
||||
for (i in input) {
|
||||
val numbers = i.splitLongs(" ", ":")
|
||||
val eqres = numbers[0]
|
||||
val ops = numbers.drop(1)
|
||||
val combs = powThreeTable[ops.size - 1]
|
||||
for (t in 0 until combs) {
|
||||
var res = ops[0]
|
||||
for (index in 1 until ops.size) {
|
||||
val l = ops[index]
|
||||
res = when ((t / powThreeTable[index - 1]) % 3) {
|
||||
0 -> res + l
|
||||
1 -> res * l
|
||||
2 -> res * powTenTable[fastCeilLog10(l)] + l
|
||||
else -> 0
|
||||
}
|
||||
if (res > eqres) break
|
||||
}
|
||||
if (res == eqres) {
|
||||
sum += eqres
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day07_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 3749L)
|
||||
check(testInputPart2Result == 11387L)
|
||||
|
||||
val input = readInput("aoc2024/Day07")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 8: Resonant Collinearity ---
|
||||
https://adventofcode.com/2024/day/8
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
............
|
||||
........0...
|
||||
.....0......
|
||||
.......0....
|
||||
....0.......
|
||||
......A.....
|
||||
............
|
||||
............
|
||||
........A...
|
||||
.........A..
|
||||
............
|
||||
............
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
|
||||
val antennas = grid.collectMatches { it != '.' }.groupBy({ it.first }) { it.second }
|
||||
val antinodes = HashSet<RelPos>()
|
||||
for (at in antennas.values) {
|
||||
for ((i1, p1) in at.withIndex()) {
|
||||
for (i2 in i1 + 1 until at.size) {
|
||||
val p2 = at[i2]
|
||||
val ap1 = RelPos(p2.dc + (p2.dc - p1.dc), p2.dr + (p2.dr - p1.dr))
|
||||
val ap2 = RelPos(p1.dc - (p2.dc - p1.dc), p1.dr - (p2.dr - p1.dr))
|
||||
if (grid.isInside(ap1)) antinodes.add(ap1)
|
||||
if (grid.isInside(ap2)) antinodes.add(ap2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return antinodes.size
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
|
||||
val antennas = grid.collectMatches { it != '.' }.groupBy({ it.first }) { it.second }
|
||||
val antinodes = HashSet<Pair<RelPos, RelPos>>()
|
||||
for (at in antennas.values) {
|
||||
for ((i1, p1) in at.withIndex()) {
|
||||
for (i2 in i1 + 1 until at.size) {
|
||||
val p2 = at[i2]
|
||||
val dc = p2.dc - p1.dc
|
||||
val dr = p2.dr - p1.dr
|
||||
val sc = p1.dc
|
||||
val sr = p1.dr
|
||||
antinodes.add(RelPos(sc, sr) to RelPos(dc, dr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return grid.generateGridPos().count { pos ->
|
||||
antinodes.any {
|
||||
val rr = pos.dr - it.first.dr
|
||||
val rc = pos.dc - it.first.dc
|
||||
val f1 = rr / it.second.dr
|
||||
val f2 = rc / it.second.dc
|
||||
((rr % it.second.dr) == 0) &&
|
||||
((rc % it.second.dc) == 0) &&
|
||||
(f1 == f2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day08_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 14)
|
||||
//check(testInputPart2Result == 34)
|
||||
|
||||
val input = readInput("aoc2024/Day08")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import java.util.*
|
||||
|
||||
/*
|
||||
--- Day 9: Disk Fragmenter ---
|
||||
https://adventofcode.com/2024/day/9
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """2333133121414131402"""
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val diskSize = input[0].sumOf { it - '0' }
|
||||
val bitmap = IntArray(diskSize) { -1 }
|
||||
var pos = 0
|
||||
var isFile = true
|
||||
var fileId = 0
|
||||
var usedBlocks = 0
|
||||
for (c in input[0]) {
|
||||
val size = c - '0'
|
||||
if (isFile) {
|
||||
usedBlocks += size
|
||||
for (p in 1..size) {
|
||||
bitmap[pos++] = fileId
|
||||
}
|
||||
fileId++
|
||||
} else {
|
||||
pos += size
|
||||
}
|
||||
isFile = !isFile
|
||||
}
|
||||
var backPos = diskSize - 1
|
||||
for (i in 0 until usedBlocks) {
|
||||
if (bitmap[i] == -1) {
|
||||
while (bitmap[backPos] < 0) {
|
||||
backPos--
|
||||
}
|
||||
bitmap[i] = bitmap[backPos]
|
||||
bitmap[backPos--] = -1
|
||||
}
|
||||
}
|
||||
return bitmap.take(usedBlocks).map(Int::toLong).reduceIndexed { index, acc, i -> acc + index * i }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
var pos = 0
|
||||
var isFile = true
|
||||
val freeList = Array<MutableSet<Int>>(10) { TreeSet() }
|
||||
val fileList = ArrayList<Pair<Int, Int>>((input[0].length + 1) / 2)
|
||||
for (c in input[0]) {
|
||||
val size = c - '0'
|
||||
if (isFile) {
|
||||
fileList.add(pos to size)
|
||||
} else if (size > 0) {
|
||||
freeList[size].add(pos)
|
||||
}
|
||||
pos += size
|
||||
isFile = !isFile
|
||||
}
|
||||
|
||||
val newFileList = Array<Pair<Int, Int>?>(fileList.size) { null }
|
||||
var revPos = fileList.size
|
||||
for (file in fileList.asReversed()) {
|
||||
val (filePos, size) = file
|
||||
var bestListSize = 0
|
||||
var bestListPos = 0
|
||||
for (s in size..9) {
|
||||
val list = freeList[s]
|
||||
val firstPos = list.firstOrNull()
|
||||
if ((firstPos != null && firstPos < filePos) &&
|
||||
(bestListSize == 0 || firstPos < bestListPos)
|
||||
) {
|
||||
bestListSize = s
|
||||
bestListPos = firstPos
|
||||
}
|
||||
}
|
||||
if (bestListSize != 0) {
|
||||
val newPos = freeList[bestListSize].first()
|
||||
freeList[bestListSize].remove(newPos)
|
||||
if (bestListSize > size) freeList[bestListSize - size].add(newPos + size)
|
||||
newFileList[--revPos] = newPos to size
|
||||
} else {
|
||||
newFileList[--revPos] = file
|
||||
// stop if not even a size 1 file could be fit
|
||||
if (size == 1) break
|
||||
}
|
||||
}
|
||||
while (revPos >= 0) {
|
||||
newFileList[revPos] = fileList[revPos]
|
||||
revPos--
|
||||
}
|
||||
// Gauß to the rescue!
|
||||
return newFileList.mapIndexed { index, file -> index * (file!!.first.toLong() * file.second + (file.second * (file.second - 1) / 2)) }
|
||||
.sum()
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day09_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 1928L)
|
||||
check(testInputPart2Result == 2858L)
|
||||
|
||||
val input = readInput("aoc2024/Day09")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 10: Hoof It ---
|
||||
https://adventofcode.com/2024/day/10
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
89010123
|
||||
78121874
|
||||
87430965
|
||||
96549874
|
||||
45678903
|
||||
32019012
|
||||
01329801
|
||||
10456732
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val starts = grid.findMatchesRelPos { it == '0' }
|
||||
var sum = 0
|
||||
for (startPos in starts) {
|
||||
var nodes = listOf(startPos)
|
||||
for (c in '1'..'9') {
|
||||
nodes = nodes.flatMap { grid.marchMatching(it, CharGrid.PLUS_POS) { char -> char == c } }.distinct()
|
||||
if (nodes.isEmpty()) break
|
||||
}
|
||||
sum += nodes.size
|
||||
}
|
||||
|
||||
return sum
|
||||
}
|
||||
|
||||
fun walkTrail(grid: CharGrid, c: Char, p: RelPos): Int {
|
||||
val newNodes = grid.marchMatching(p, CharGrid.PLUS_POS) { it == c }
|
||||
return if (c == '9') newNodes.size else newNodes.sumOf { walkTrail(grid, c + 1, it) }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
return grid.findMatchesRelPos { it == '0' }.sumOf { walkTrail(grid, '1', it) }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day10_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 36)
|
||||
check(testInputPart2Result == 81)
|
||||
|
||||
val input = readInput("aoc2024/Day10")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitLongs
|
||||
|
||||
/*
|
||||
--- Day 11: Plutonian Pebbles ---
|
||||
https://adventofcode.com/2024/day/11
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
125 17
|
||||
"""
|
||||
|
||||
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
|
||||
val lookupTable = HashMap<Pair<Long, Int>, Long>()
|
||||
|
||||
fun fastCeilLog10(n: Long): Int {
|
||||
for (i in 1 until powTenTable.size) {
|
||||
if (powTenTable[i] > n) return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun rec(n: Long, i: Int): Long =
|
||||
if (i == 0) 1 else lookupTable.getOrPut(n to i) {
|
||||
if (n == 0L) {
|
||||
rec(1L, i - 1)
|
||||
} else {
|
||||
val size = fastCeilLog10(n)
|
||||
if (size and 1 == 0) {
|
||||
val left = n / powTenTable[size / 2]
|
||||
val right = n % powTenTable[size / 2]
|
||||
rec(left, i - 1) + rec(right, i - 1)
|
||||
} else {
|
||||
rec(n * 2024L, i - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val numbers = input[0].splitLongs()
|
||||
return numbers.sumOf { rec(it, 25) }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val numbers = input[0].splitLongs()
|
||||
return numbers.sumOf { rec(it, 75) }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day11_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 55312L)
|
||||
//check(testInputPart2Result == 0L)
|
||||
|
||||
val input = readInput("aoc2024/Day11")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 12: Garden Groups ---
|
||||
https://adventofcode.com/2024/day/12
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
RRRRIICCFF
|
||||
RRRRIICCCF
|
||||
VVRRRCCFFF
|
||||
VVRCCCJFFF
|
||||
VVVVCJJCFE
|
||||
VVIVCCJJEE
|
||||
VVIIICJJEE
|
||||
MIIIIIJJEE
|
||||
MIIISIJEEE
|
||||
MMMISSJEEE
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
var sum = 0
|
||||
for (p in grid.generateGridPos()) {
|
||||
val c = grid[p]
|
||||
if (c != '.') {
|
||||
val areaPos = HashSet<RelPos>()
|
||||
val fence = HashSet<RelPos>()
|
||||
var newPos = setOf(p)
|
||||
while (newPos.isNotEmpty()) {
|
||||
val newSeeds = HashSet<RelPos>()
|
||||
for (np in newPos) {
|
||||
areaPos.add(np)
|
||||
grid[np] = c.lowercaseChar()
|
||||
newSeeds.addAll(grid.matchAbsoluteRelPos(np, CharGrid.PLUS_POS) { it == c })
|
||||
fence.addAll(grid.matchRelative(np.dc, np.dr, CharGrid.PLUS_POS) { it.uppercaseChar() != c }
|
||||
.map { RelPos(np.dc * 2 + it.dc, np.dr * 2 + it.dr) })
|
||||
}
|
||||
newPos = newSeeds
|
||||
}
|
||||
areaPos.forEach { grid[it] = '.' }
|
||||
sum += areaPos.size * fence.size
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
var sum = 0
|
||||
val magic = listOf(3, 5, 10, 12)
|
||||
val star = CharGrid.PLUS_POS.plus(CharGrid.CROSS_POS)
|
||||
for (p in grid.generateGridPos()) {
|
||||
val c = grid[p]
|
||||
if (c != '.') {
|
||||
val areaPos = HashSet<RelPos>()
|
||||
var newPos = setOf(p)
|
||||
while (newPos.isNotEmpty()) {
|
||||
val newSeeds = HashSet<RelPos>()
|
||||
for (np in newPos) {
|
||||
areaPos.add(np)
|
||||
grid[np] = '.'
|
||||
newSeeds.addAll(grid.matchAbsoluteRelPos(np, CharGrid.PLUS_POS) { it == c })
|
||||
}
|
||||
newPos = newSeeds
|
||||
}
|
||||
val corners = areaPos.sumOf {
|
||||
val b = star.mapIndexed { i, rp -> if (areaPos.contains(it.translate(rp))) 1 shl i else 0 }.sum()
|
||||
val outer = magic.count { b and it == 0 }
|
||||
val inner = magic.filterIndexed { i, m -> (b and m == m) && (b and (16 shl i) == 0) }.count()
|
||||
outer + inner
|
||||
}
|
||||
sum += areaPos.size * corners
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day12_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 1930)
|
||||
check(testInputPart2Result == 1206)
|
||||
|
||||
val input = readInput("aoc2024/Day12")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 13: Claw Contraption ---
|
||||
https://adventofcode.com/2024/day/13
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
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
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
var i = 0
|
||||
var sum = 0
|
||||
while (i < input.size) {
|
||||
val (ax, ay) = "Button A: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
|
||||
val (bx, by) = "Button B: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
|
||||
val (px, py) = "Prize: X=(\\d+), Y=(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
|
||||
i++
|
||||
val xpos = IntArray(101 * 101)
|
||||
val ypos = IntArray(101 * 101)
|
||||
val solutions = ArrayList<Int>()
|
||||
for (b in 1..100) {
|
||||
for (a in 1..100) {
|
||||
val op = (a - 1) + (b - 1) * 101
|
||||
if ((xpos[op] == px) && (ypos[op] == py)) {
|
||||
solutions.add(op)
|
||||
}
|
||||
if ((xpos[op] > px) || (ypos[op] > py)) break
|
||||
xpos[a + (b - 1) * 101] = xpos[op] + ax
|
||||
xpos[(a - 1) + b * 101] = xpos[op] + bx
|
||||
ypos[a + (b - 1) * 101] = ypos[op] + ay
|
||||
ypos[(a - 1) + b * 101] = ypos[op] + by
|
||||
}
|
||||
}
|
||||
val minTokens = solutions.minOfOrNull { (it % 101) * 3 + (it / 101) }
|
||||
sum += minTokens ?: 0
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
var i = 0
|
||||
var sum = 0L
|
||||
|
||||
// 1. ax * pa + bx * pb = px
|
||||
// 2. ay * pa + by * pb = py
|
||||
// 3. pa * 3 + pb <- min
|
||||
// 4. pa, pb in N (int > 0)
|
||||
|
||||
// ax * pa + bx * pb - px = ay * pa + by * pb - py
|
||||
// (ax - ay) * pa + (bx - by) * pb + py - px = 0
|
||||
// (ax - ay) * pa + (bx - by) * pb = px - py
|
||||
|
||||
while (i < input.size) {
|
||||
val (ax, ay) = "Button A: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() }
|
||||
val (bx, by) = "Button B: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() }
|
||||
val (px, py) = "Prize: X=(\\d+), Y=(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() + 10000000000000L }
|
||||
i++
|
||||
|
||||
// val t1 = ax - ay
|
||||
// val t2 = bx - by
|
||||
// val t3 = px - py
|
||||
|
||||
// Linear Diophantine equations
|
||||
/* The simplest linear Diophantine equation takes the form
|
||||
a*x + b*y = c
|
||||
where a, b and c are given integers.
|
||||
The solutions are described by the following theorem:
|
||||
This Diophantine equation has a solution (where x and y are integers),
|
||||
IFF c is a multiple of the greatest common divisor of a and b.
|
||||
Moreover, if (x, y) is a solution, then the other solutions have the
|
||||
form (x + kv, y − ku), where
|
||||
- k is an arbitrary integer, and
|
||||
- u and v are the quotients of a and b (respectively) by the greatest common divisor of a and b.
|
||||
*/
|
||||
// With our input data, all equations have exactly one solution
|
||||
val pad = (px * by - py * bx)
|
||||
val pan = (ax * by - ay * bx)
|
||||
if (pad % pan == 0L) {
|
||||
val pa = pad / pan
|
||||
val pbd = (px - pa * ax)
|
||||
if (pbd % bx == 0L) {
|
||||
val pb = pbd / bx
|
||||
sum += pa * 3 + pb
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day13_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 480)
|
||||
//check(testInputPart2Result == 0L)
|
||||
|
||||
val input = readInput("aoc2024/Day13")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 14: Restroom Redoubt ---
|
||||
https://adventofcode.com/2024/day/14
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
p=0,4 v=3,-3
|
||||
p=6,3 v=-1,-3
|
||||
p=10,3 v=-1,2
|
||||
p=2,0 v=2,-1
|
||||
p=0,0 v=1,3
|
||||
p=3,0 v=-2,-2
|
||||
p=7,6 v=-1,-3
|
||||
p=3,0 v=-1,-2
|
||||
p=9,3 v=2,3
|
||||
p=7,3 v=-1,2
|
||||
p=2,4 v=2,-3
|
||||
p=9,5 v=-3,-3
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>, time: Int, w: Int, h: Int): Int {
|
||||
val robots = input.map { "p=(\\d+),(\\d+) v=(-?\\d+),(-?\\d+)".toRegex().find(it)!!.groupValues.drop(1).map { it.toInt() }.toIntArray() }
|
||||
for (r in robots) {
|
||||
r[0] = (r[0] + time * (r[2] + w)) % w
|
||||
r[1] = (r[1] + time * (r[3] + h)) % h
|
||||
}
|
||||
val q1 = robots.count { it[0] < w / 2 && it[1] < h / 2 }
|
||||
val q2 = robots.count { it[0] > w / 2 && it[1] < h / 2 }
|
||||
val q3 = robots.count { it[0] < w / 2 && it[1] > h / 2 }
|
||||
val q4 = robots.count { it[0] > w / 2 && it[1] > h / 2 }
|
||||
return q1 * q2 * q3 * q4
|
||||
}
|
||||
|
||||
fun part2(input: List<String>, w: Int, h: Int): Int {
|
||||
val robots = input.map { "p=(\\d+),(\\d+) v=(-?\\d+),(-?\\d+)".toRegex().find(it)!!.groupValues.drop(1).map { it.toInt() }.toIntArray() }
|
||||
/*
|
||||
either: 1) x == w/2 -+ c, y == c c in [0..w/2]
|
||||
or : 2) x == w/2, y in (w/2..h)
|
||||
|
||||
1) px + t * (vx + w) % w = w/2 - c; py + t * (vy + h) % h = c
|
||||
px + t * (vx + w) % w = w/2 - (py + t * (vy + h) % h)
|
||||
(px - py) + t * (((vx + w) % w) - ((vy + h) % h)) = w/2
|
||||
|
||||
2) px == w/2, py > w/2 % h
|
||||
*/
|
||||
val z = Array(robots.size) { IntArray(4) }
|
||||
for (t in 1..w * h) {
|
||||
/*val good = robots.all {
|
||||
val x = ((it[0] + t * (it[2] + w)) % w).toInt()
|
||||
val y = ((it[1] + t * (it[3] + h)) % h).toInt()
|
||||
(y <= w / 2 && ((x >= w / 2 - y) && (x <= w / 2 + y))) || (x == w / 2 && y > w / 2)
|
||||
}
|
||||
if (!good) continue*/
|
||||
for ((i, r) in robots.withIndex()) {
|
||||
z[i][0] = ((r[0] + t * (r[2] + w)) % w).toInt()
|
||||
z[i][1] = ((r[1] + t * (r[3] + h)) % h).toInt()
|
||||
}
|
||||
if (z.map { it[0] to it[1] }.distinct().count() < robots.size) continue
|
||||
println("Solution $t")
|
||||
val grid = CharGrid(w, h, '.')
|
||||
z.forEach { grid[it[0], it[1]] = '*' }
|
||||
grid.debug()
|
||||
println()
|
||||
return t
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day14_test")
|
||||
val testInputPart1Result = part1(testInput, 100, 11, 7)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
//val testInputPart2Result = part2(testInput, 11, 7)
|
||||
//println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 12)
|
||||
//check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2024/Day14")
|
||||
part1(input, 100, 101, 103).println()
|
||||
part2(input, 101, 103).println()
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 15: Warehouse Woes ---
|
||||
https://adventofcode.com/2024/day/15
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput2 = """
|
||||
##########
|
||||
#..O..O.O#
|
||||
#......O.#
|
||||
#.OO..O.O#
|
||||
#..O@..O.#
|
||||
#O#..O...#
|
||||
#O..O..O.#
|
||||
#.OO.O.OO#
|
||||
#....O...#
|
||||
##########
|
||||
|
||||
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
|
||||
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
|
||||
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
|
||||
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
|
||||
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
|
||||
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
|
||||
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
|
||||
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
|
||||
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
|
||||
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
|
||||
"""
|
||||
|
||||
val inlineTestInput = """
|
||||
########
|
||||
#..O.O.#
|
||||
##@.O..#
|
||||
#...O..#
|
||||
#.#.O..#
|
||||
#...O..#
|
||||
#......#
|
||||
########
|
||||
|
||||
<^^>>>vv<v>>v<<
|
||||
"""
|
||||
|
||||
val inlineTestInput3 = """
|
||||
#######
|
||||
#...#.#
|
||||
#.....#
|
||||
#..OO@#
|
||||
#..O..#
|
||||
#.....#
|
||||
#######
|
||||
|
||||
<vv<<^^<<^^
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val splitp = input.withIndex().find { it.value.isEmpty() }!!.index
|
||||
val grid = CharGrid(input.take(splitp))
|
||||
val inst = input.drop(splitp).joinToString("")
|
||||
var (rc, rd) = grid.findMatches { it == '@' }[0]
|
||||
for (m in inst) {
|
||||
grid[rc, rd] = '.'
|
||||
var dc = 0
|
||||
var dr = 0
|
||||
when (m) {
|
||||
'<' -> dc = -1
|
||||
'>' -> dc = 1
|
||||
'^' -> dr = -1
|
||||
'v' -> dr = 1
|
||||
}
|
||||
var s = 1
|
||||
while (grid[rc + dc * s, rd + dr * s] != '.') {
|
||||
if (grid[rc + dc * s, rd + dr * s] == '#') {
|
||||
s = 0
|
||||
break
|
||||
}
|
||||
s++
|
||||
}
|
||||
if (s > 0) {
|
||||
if (s > 1) {
|
||||
grid[rc + dc * s, rd + dr * s] = 'O'
|
||||
}
|
||||
rc += dc
|
||||
rd += dr
|
||||
}
|
||||
grid[rc, rd] = '@'
|
||||
}
|
||||
return grid.generateGridPos().filter { grid[it] == 'O' }.sumOf { it.dc + it.dr * 100 }
|
||||
}
|
||||
|
||||
fun canMove(grid: CharGrid, bc: Int, rd: Int, dr: Int): Boolean {
|
||||
if ((grid[bc, rd] == '[')) {
|
||||
val c1 = grid[bc, rd + dr]
|
||||
val c2 = grid[bc + 1, rd + dr]
|
||||
if ((c1 == '.') && (c2 == '.')) return true
|
||||
if ((c1 == '#') || (c2 == '#')) return false
|
||||
if ((c1 == '[')) return canMove(grid, bc, rd + dr, dr)
|
||||
return (if (c1 == ']') canMove(grid, bc - 1, rd + dr, dr) else true)
|
||||
&& (if (c2 == '[') canMove(grid, bc + 1, rd + dr, dr) else true)
|
||||
} else if ((grid[bc, rd] == ']')) {
|
||||
return canMove(grid, bc - 1, rd, dr)
|
||||
} else {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
fun doMove(grid: CharGrid, bc: Int, rd: Int, dr: Int) {
|
||||
if ((grid[bc, rd] == '[')) {
|
||||
val c1 = grid[bc, rd + dr]
|
||||
val c2 = grid[bc + 1, rd + dr]
|
||||
if ((c1 == '[')) {
|
||||
doMove(grid, bc, rd + dr, dr)
|
||||
} else {
|
||||
if (c1 == ']') doMove(grid, bc - 1, rd + dr, dr)
|
||||
if (c2 == '[') doMove(grid, bc + 1, rd + dr, dr)
|
||||
}
|
||||
if ((grid[bc, rd + dr] == '.') && (grid[bc + 1, rd + dr] == '.')) {
|
||||
grid[bc, rd + dr] = '['
|
||||
grid[bc + 1, rd + dr] = ']'
|
||||
grid[bc, rd] = '.'
|
||||
grid[bc + 1, rd] = '.'
|
||||
} else {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
} else if ((grid[bc, rd] == ']')) {
|
||||
doMove(grid, bc - 1, rd, dr)
|
||||
}
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val splitp = input.withIndex().find { it.value.isEmpty() }!!.index
|
||||
val sgrid = CharGrid(input.take(splitp))
|
||||
val grid = CharGrid(sgrid.width * 2, sgrid.height)
|
||||
sgrid.generateGridPos().forEach {
|
||||
when (val cc = sgrid[it]) {
|
||||
'@' -> {
|
||||
grid[it.dc * 2, it.dr] = cc
|
||||
grid[it.dc * 2 + 1, it.dr] = '.'
|
||||
}
|
||||
|
||||
'O' -> {
|
||||
grid[it.dc * 2, it.dr] = '['
|
||||
grid[it.dc * 2 + 1, it.dr] = ']'
|
||||
}
|
||||
|
||||
else -> {
|
||||
grid[it.dc * 2, it.dr] = cc
|
||||
grid[it.dc * 2 + 1, it.dr] = cc
|
||||
}
|
||||
}
|
||||
}
|
||||
val inst = input.drop(splitp).joinToString("")
|
||||
var (rc, rd) = grid.findMatches { it == '@' }[0]
|
||||
for (m in inst) {
|
||||
grid[rc, rd] = '.'
|
||||
var dc = 0
|
||||
var dr = 0
|
||||
when (m) {
|
||||
'<' -> dc = -1
|
||||
'>' -> dc = 1
|
||||
'^' -> dr = -1
|
||||
'v' -> dr = 1
|
||||
}
|
||||
if (dc != 0) {
|
||||
var s = 1
|
||||
var bc: Char? = null
|
||||
while (grid[rc + dc * s, rd] != '.') {
|
||||
val cc = grid[rc + dc * s, rd]
|
||||
if (cc == '#') {
|
||||
s = 0
|
||||
break
|
||||
} else {
|
||||
if (bc != null && cc == bc) {
|
||||
s = 0
|
||||
break
|
||||
}
|
||||
bc = cc
|
||||
}
|
||||
s++
|
||||
}
|
||||
if (s > 0) {
|
||||
if (s > 1) {
|
||||
for (p in s downTo 1) {
|
||||
grid[rc + dc * p, rd] = grid[rc + dc * (p - 1), rd]
|
||||
}
|
||||
}
|
||||
rc += dc
|
||||
rd += dr
|
||||
}
|
||||
} else {
|
||||
if (grid[rc, rd + dr] != '.') {
|
||||
if (grid[rc, rd + dr] != '#') {
|
||||
if (canMove(grid, rc, rd + dr, dr)) {
|
||||
doMove(grid, rc, rd + dr, dr)
|
||||
rc += dc
|
||||
rd += dr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rc += dc
|
||||
rd += dr
|
||||
}
|
||||
}
|
||||
grid[rc, rd] = '@'
|
||||
}
|
||||
return grid.generateGridPos().filter { grid[it] == '[' }.sumOf { it.dc + it.dr * 100 }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
val testInput3 = inlineTestInput3.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day15_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput2)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 2028)
|
||||
check(testInputPart2Result == 9021)
|
||||
|
||||
val input = readInput("aoc2024/Day15")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 16: Reindeer Maze ---
|
||||
https://adventofcode.com/2024/day/16
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
###############
|
||||
#.......#....E#
|
||||
#.#.###.#.###.#
|
||||
#.....#.#...#.#
|
||||
#.###.#####.#.#
|
||||
#.#.#.......#.#
|
||||
#.#.#####.###.#
|
||||
#...........#.#
|
||||
###.#.#####.#.#
|
||||
#...#.....#.#.#
|
||||
#.#.#.###.#.#.#
|
||||
#.....#...#.#.#
|
||||
#.###.#.#.#.#.#
|
||||
#S..#.....#...#
|
||||
###############
|
||||
"""
|
||||
val inlineTestInput2 = """
|
||||
#################
|
||||
#...#...#...#..E#
|
||||
#.#.#.#.#.#.#.#.#
|
||||
#.#.#.#...#...#.#
|
||||
#.#.#.#.###.#.#.#
|
||||
#...#.#.#.....#.#
|
||||
#.#.#.#.#.#####.#
|
||||
#.#...#.#.#.....#
|
||||
#.#.#####.#.###.#
|
||||
#.#.#.......#...#
|
||||
#.#.###.#####.###
|
||||
#.#.#...#.....#.#
|
||||
#.#.#.#####.###.#
|
||||
#.#.#.........#.#
|
||||
#.#.#.#########.#
|
||||
#S#.............#
|
||||
#################
|
||||
"""
|
||||
|
||||
data class Node(val pos: RelPos, val dir: RelPos, val relLen: Int, val path: LinkedHashSet<Pair<RelPos, RelPos>>)
|
||||
|
||||
val costMap = hashMapOf(
|
||||
RelPos(0, -1) to listOf(RelPos(0, -1) to 1, RelPos(1, 0) to 1001, RelPos(-1, 0) to 1001, RelPos(0, 1) to 2001),
|
||||
RelPos(1, 0) to listOf(RelPos(1, 0) to 1, RelPos(0, 1) to 1001, RelPos(0, -1) to 1001, RelPos(-1, 0) to 2001),
|
||||
RelPos(0, 1) to listOf(RelPos(0, 1) to 1, RelPos(-1, 0) to 1001, RelPos(1, 0) to 1001, RelPos(0, -1) to 2001),
|
||||
RelPos(-1, 0) to listOf(RelPos(-1, 0) to 1, RelPos(0, 1) to 1001, RelPos(0, -1) to 1001, RelPos(1, 0) to 2001)
|
||||
)
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input, '#')
|
||||
val queue = ArrayDeque<Node>()
|
||||
val (startc, startr) = grid.findMatches { it == 'S' }[0]
|
||||
val (endc, endr) = grid.findMatches { it == 'E' }[0]
|
||||
val endpos = RelPos(endc, endr)
|
||||
|
||||
grid[startc, startr] = '.'
|
||||
grid[endpos] = '.'
|
||||
queue.add(Node(RelPos(startc, startr), RelPos(1, 0), 0, LinkedHashSet()))
|
||||
val bestCosts = HashMap<Pair<RelPos, RelPos>, Int>()
|
||||
var minCost = Int.MAX_VALUE
|
||||
while (queue.isNotEmpty()) {
|
||||
val node = queue.removeFirst()
|
||||
val pos = node.pos
|
||||
val dir = node.dir
|
||||
val cost = node.relLen
|
||||
val path = node.path
|
||||
path.add(pos to dir)
|
||||
if (pos == endpos) {
|
||||
minCost = minCost.coerceAtMost(cost)
|
||||
continue
|
||||
}
|
||||
if ((bestCosts[pos to dir] ?: Int.MAX_VALUE) <= cost) {
|
||||
continue
|
||||
}
|
||||
bestCosts[pos to dir] = cost
|
||||
val nextDirs =
|
||||
costMap[dir]!!.filter { grid[pos.translate(it.first)] == '.' && cost + it.second < minCost && !path.contains(pos.translate(it.first) to it.first) }
|
||||
.map { Node(pos.translate(it.first), it.first, cost + it.second, LinkedHashSet(path)) }
|
||||
|
||||
queue.addAll(nextDirs)
|
||||
}
|
||||
|
||||
return minCost
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input, '#')
|
||||
val queue = ArrayDeque<Node>()
|
||||
val (startc, startr) = grid.findMatches { it == 'S' }[0]
|
||||
val (endc, endr) = grid.findMatches { it == 'E' }[0]
|
||||
val endpos = RelPos(endc, endr)
|
||||
|
||||
grid[startc, startr] = '.'
|
||||
grid[endpos] = '.'
|
||||
queue.add(Node(RelPos(startc, startr), RelPos(1, 0), 0, LinkedHashSet()))
|
||||
val bestCosts = HashMap<Pair<RelPos, RelPos>, Int>()
|
||||
var minCost = Int.MAX_VALUE
|
||||
val bestNodes = HashSet<RelPos>()
|
||||
while (queue.isNotEmpty()) {
|
||||
val node = queue.removeFirst()
|
||||
val pos = node.pos
|
||||
val dir = node.dir
|
||||
val cost = node.relLen
|
||||
val path = node.path
|
||||
path.add(pos to dir)
|
||||
if (pos == endpos) {
|
||||
if (cost < minCost) {
|
||||
bestNodes.clear()
|
||||
bestNodes.addAll(path.map { it.first })
|
||||
}
|
||||
if (minCost == cost) {
|
||||
bestNodes.addAll(path.map { it.first })
|
||||
}
|
||||
minCost = minCost.coerceAtMost(cost)
|
||||
continue
|
||||
}
|
||||
if ((bestCosts[pos to dir] ?: Int.MAX_VALUE) < cost) {
|
||||
continue
|
||||
}
|
||||
bestCosts[pos to dir] = cost
|
||||
val nextDirs =
|
||||
costMap[dir]!!.filter { grid[pos.translate(it.first)] == '.' && cost + it.second < minCost && !path.contains(pos.translate(it.first) to it.first) }
|
||||
.map { Node(pos.translate(it.first), it.first, cost + it.second, LinkedHashSet(path)) }
|
||||
|
||||
queue.addAll(nextDirs)
|
||||
}
|
||||
|
||||
return bestNodes.size
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day16_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart1Result2 = part1(testInput2)
|
||||
println("Part 1 Test 2: $testInputPart1Result2")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
val testInputPart2Result2 = part2(testInput2)
|
||||
println("Part 2 Test2: $testInputPart2Result2")
|
||||
check(testInputPart1Result == 7036)
|
||||
check(testInputPart1Result2 == 11048)
|
||||
check(testInputPart2Result == 45)
|
||||
check(testInputPart2Result2 == 64)
|
||||
|
||||
val input = readInput("aoc2024/Day16")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
|
||||
/*
|
||||
--- Day 17: Chronospatial Computer ---
|
||||
https://adventofcode.com/2024/day/17
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
Register A: 117440
|
||||
Register B: 0
|
||||
Register C: 0
|
||||
|
||||
Program: 0,3,5,4,3,0
|
||||
"""
|
||||
val regs = LongArray(3)
|
||||
|
||||
fun getComboOperand(op: Int): Long =
|
||||
when (op) {
|
||||
0, 1, 2, 3 -> op.toLong()
|
||||
4, 5, 6 -> regs[op - 4]
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): String {
|
||||
regs[0] = input[0].split(" ")[2].toLong()
|
||||
regs[1] = input[1].split(" ")[2].toLong()
|
||||
regs[2] = input[2].split(" ")[2].toLong()
|
||||
val ops = input[4].split(" ")[1].splitInts(",")
|
||||
val outs = ArrayList<Int>()
|
||||
var pc = 0
|
||||
while (pc < ops.size) {
|
||||
when (ops[pc++]) {
|
||||
// adv
|
||||
0 -> regs[0] = regs[0] shr getComboOperand(ops[pc++]).toInt()
|
||||
// bdv
|
||||
6 -> regs[1] = regs[0] shr getComboOperand(ops[pc++]).toInt()
|
||||
// cdv
|
||||
7 -> regs[2] = regs[0] shr getComboOperand(ops[pc++]).toInt()
|
||||
// bxl
|
||||
1 -> regs[1] = regs[1] xor ops[pc++].toLong()
|
||||
// bst
|
||||
2 -> regs[1] = getComboOperand(ops[pc++]) and 7
|
||||
// jnz
|
||||
3 -> if (regs[0] == 0L) pc++ else pc = ops[pc]
|
||||
// bxc
|
||||
4 -> {
|
||||
regs[1] = regs[1] xor regs[2]
|
||||
pc++
|
||||
}
|
||||
// out
|
||||
5 -> outs.add((getComboOperand(ops[pc++]) and 7).toInt())
|
||||
}
|
||||
}
|
||||
return outs.joinToString(",")
|
||||
}
|
||||
|
||||
fun verify(rega: Long, ops: List<Int>, outs: List<Int>): Boolean {
|
||||
regs[0] = rega
|
||||
var pc = 0
|
||||
var outpos = 0
|
||||
while (pc < ops.size) {
|
||||
when (ops[pc++]) {
|
||||
// adv
|
||||
0 -> regs[0] = regs[0] shr getComboOperand(ops[pc++]).toInt()
|
||||
// bdv
|
||||
6 -> regs[1] = regs[0] shr getComboOperand(ops[pc++]).toInt()
|
||||
// cdv
|
||||
7 -> regs[2] = regs[0] shr getComboOperand(ops[pc++]).toInt()
|
||||
// bxl
|
||||
1 -> regs[1] = regs[1] xor ops[pc++].toLong()
|
||||
// bst
|
||||
2 -> regs[1] = getComboOperand(ops[pc++]) and 7
|
||||
// jnz
|
||||
3 -> if (regs[0] == 0L) pc++ else pc = ops[pc]
|
||||
// bxc
|
||||
4 -> {
|
||||
regs[1] = regs[1] xor regs[2]
|
||||
pc++
|
||||
}
|
||||
// out
|
||||
5 -> {
|
||||
val o = (getComboOperand(ops[pc++]) and 7).toInt()
|
||||
if (outs[outpos++] != o) return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun rec(a: Long, p: Int, ops: List<Int>, outs: List<Int>): Long {
|
||||
val partOutput = outs.take(p).reversed()
|
||||
// assuming that all programs use 3 bit shifts in the last operation
|
||||
for (i in 0L..7L) {
|
||||
if (verify((a shl 3) or i, ops, partOutput)) {
|
||||
if (p == outs.size) return (a shl 3) or i
|
||||
val res = rec((a shl 3) or i, p + 1, ops, outs)
|
||||
if (res != 0L) return res
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val ops = input[4].split(" ")[1].splitInts(",")
|
||||
val outs = ops.reversed()
|
||||
return rec(0L, 1, ops, outs)
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day17_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
|
||||
val input = readInput("aoc2024/Day17")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
import java.util.*
|
||||
|
||||
/*
|
||||
--- Day 18: RAM Run ---
|
||||
https://adventofcode.com/2024/day/18
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
5,4
|
||||
4,2
|
||||
4,5
|
||||
3,0
|
||||
2,1
|
||||
6,3
|
||||
2,4
|
||||
1,5
|
||||
0,6
|
||||
3,3
|
||||
2,6
|
||||
5,1
|
||||
1,2
|
||||
5,5
|
||||
2,5
|
||||
6,5
|
||||
1,4
|
||||
0,4
|
||||
6,4
|
||||
1,1
|
||||
6,1
|
||||
1,0
|
||||
0,5
|
||||
1,6
|
||||
2,0
|
||||
"""
|
||||
|
||||
data class Node(val pos: RelPos, val relLen: Int)
|
||||
|
||||
fun part1(input: List<String>, steps: Int, wh: Int): Int {
|
||||
val posList = input.map { it.splitInts(",").toIntArray() }
|
||||
val grid = CharGrid(wh, wh, '#', fillChar = '.')
|
||||
posList.take(steps).forEach { grid[it[0], it[1]] = '#' }
|
||||
val queue = PriorityQueue<Node>(Comparator.comparing { -it.pos.dc - it.pos.dr })
|
||||
queue.add(Node(RelPos(0, 0), 0))
|
||||
val bestCosts = HashMap<RelPos, Int>()
|
||||
var minCost = Int.MAX_VALUE
|
||||
while (queue.isNotEmpty()) {
|
||||
val node = queue.remove()
|
||||
val pos = node.pos
|
||||
val cost = node.relLen
|
||||
if (pos.dc == wh - 1 && pos.dr == wh - 1) {
|
||||
minCost = minCost.coerceAtMost(cost)
|
||||
continue
|
||||
}
|
||||
if ((bestCosts[pos] ?: Int.MAX_VALUE) < cost) {
|
||||
continue
|
||||
}
|
||||
bestCosts[pos] = cost
|
||||
queue.addAll(CharGrid.PLUS_POS
|
||||
.map { pos.translate(it) }
|
||||
.filter { grid[it] == '.' && (bestCosts[it] ?: Int.MAX_VALUE) > cost + 1 }
|
||||
.map { Node(it, cost + 1) })
|
||||
}
|
||||
return minCost
|
||||
}
|
||||
|
||||
fun part2(input: List<String>, wh: Int): RelPos {
|
||||
val posList = input.map { it.splitInts(",").toIntArray() }
|
||||
var lowerBound = 0
|
||||
var higherBound = posList.size
|
||||
do {
|
||||
val grid = CharGrid(wh, wh, '#', fillChar = '.')
|
||||
val boulderPos = (lowerBound + higherBound) / 2
|
||||
posList.take(boulderPos).forEach { grid[it[0], it[1]] = '#' }
|
||||
|
||||
var foundPath = false
|
||||
val queue = PriorityQueue<Node>(Comparator.comparing { -it.pos.dc - it.pos.dr })
|
||||
queue.add(Node(RelPos(0, 0), 0))
|
||||
val bestCosts = HashMap<RelPos, Int>()
|
||||
while (queue.isNotEmpty()) {
|
||||
val node = queue.remove()
|
||||
val pos = node.pos
|
||||
val cost = node.relLen
|
||||
if (pos.dc == wh - 1 && pos.dr == wh - 1) {
|
||||
foundPath = true
|
||||
break
|
||||
}
|
||||
if ((bestCosts[pos] ?: Int.MAX_VALUE) < cost) {
|
||||
continue
|
||||
}
|
||||
bestCosts[pos] = cost
|
||||
queue.addAll(CharGrid.PLUS_POS
|
||||
.map { pos.translate(it) }
|
||||
.filter { grid[it] == '.' && (bestCosts[it] ?: Int.MAX_VALUE) > cost + 1 }
|
||||
.map { Node(it, cost + 1) })
|
||||
}
|
||||
if (foundPath) {
|
||||
lowerBound = boulderPos + 1
|
||||
} else {
|
||||
higherBound = boulderPos
|
||||
}
|
||||
} while (lowerBound < higherBound)
|
||||
println("$lowerBound $higherBound")
|
||||
return RelPos(posList[lowerBound - 1][0], posList[lowerBound - 1][1])
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day18_test")
|
||||
val testInputPart1Result = part1(testInput, 12, 7)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput, 7)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 22)
|
||||
check(testInputPart2Result == RelPos(6, 1))
|
||||
|
||||
val input = readInput("aoc2024/Day18")
|
||||
part1(input, 1024, 71).println()
|
||||
part2(input, 71).println()
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 19: Linen Layout ---
|
||||
https://adventofcode.com/2024/day/19
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
r, wr, b, g, bwu, rb, gb, br
|
||||
|
||||
brwrr
|
||||
bggr
|
||||
gbbr
|
||||
rrbgbr
|
||||
ubwu
|
||||
bwurrg
|
||||
brgr
|
||||
bbrgwb
|
||||
"""
|
||||
|
||||
fun rec(inp: String, towels: Array<String>): Boolean =
|
||||
inp.isEmpty() || towels.any { inp.startsWith(it) && rec(inp.substring(it.length), towels) }
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val towels = input[0].split(", ").toTypedArray()
|
||||
return input.drop(2).count { rec(it, towels) }
|
||||
}
|
||||
|
||||
val cache = HashMap<String, Long>()
|
||||
|
||||
fun rec2(inp: String, towels: Array<String>): Long =
|
||||
if (inp.isEmpty()) 1 else cache.getOrPut(inp) {
|
||||
towels.filter { inp.startsWith(it) }.sumOf { rec2(inp.substring(it.length), towels) }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val towels = input[0].split(", ").toTypedArray()
|
||||
return input.drop(2).sumOf { rec2(it, towels) }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day19_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 6)
|
||||
check(testInputPart2Result == 16L)
|
||||
|
||||
val input = readInput("aoc2024/Day19")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
import kotlin.math.abs
|
||||
|
||||
/*
|
||||
--- Day 20: Race Condition ---
|
||||
https://adventofcode.com/2024/day/20
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
###############
|
||||
#...#...#.....#
|
||||
#.#.#.#.#.###.#
|
||||
#S#...#.#.#...#
|
||||
#######.#.#.###
|
||||
#######.#.#...#
|
||||
#######.#.###.#
|
||||
###..E#...#...#
|
||||
###.#######.###
|
||||
#...###...#...#
|
||||
#.#####.#.###.#
|
||||
#.#...#.#.#...#
|
||||
#.#.#.#.#.#.###
|
||||
#...#...#...###
|
||||
###############
|
||||
"""
|
||||
|
||||
data class Node(val pos: RelPos, val relLen: Int)
|
||||
|
||||
fun getPathCosts(grid: CharGrid, startPos: RelPos, endPos: RelPos): HashMap<RelPos, Int> {
|
||||
val queue = ArrayDeque<Node>()
|
||||
queue.add(Node(startPos, 0))
|
||||
val bestCosts = HashMap<RelPos, Int>()
|
||||
while (queue.isNotEmpty()) {
|
||||
val node = queue.removeFirst()
|
||||
val pos = node.pos
|
||||
val cost = node.relLen
|
||||
bestCosts[pos] = cost
|
||||
if (pos == endPos) {
|
||||
break
|
||||
}
|
||||
queue.addAll(CharGrid.PLUS_POS
|
||||
.map { pos.translate(it) }
|
||||
.filter { grid[it] == '.' && !bestCosts.contains(it) }
|
||||
.map { Node(it, cost + 1) })
|
||||
}
|
||||
return bestCosts
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val start = grid.findMatchesRelPos { it == 'S' }[0]
|
||||
val end = grid.findMatchesRelPos { it == 'E' }[0]
|
||||
grid[end] = '.'
|
||||
|
||||
val cheats = listOf(
|
||||
RelPos(0, -1), RelPos(-1, 0), RelPos(1, 0), RelPos(0, 1),
|
||||
RelPos(0, -2), RelPos(-2, 0), RelPos(2, 0), RelPos(0, 2),
|
||||
)
|
||||
val pathCosts = getPathCosts(grid, start, end)
|
||||
|
||||
return pathCosts.asSequence().sumOf { (pos, cost) ->
|
||||
cheats.map { pos.translate(it) to (abs(it.dr) + abs(it.dc)) }
|
||||
.filter { (pathCosts[it.first] ?: 0) - (cost + it.second) >= 100 }
|
||||
.map { it.first to (pathCosts[it.first]!! - (cost + it.second)) }.count()
|
||||
}
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val start = grid.findMatchesRelPos { it == 'S' }[0]
|
||||
val end = grid.findMatchesRelPos { it == 'E' }[0]
|
||||
grid[end] = '.'
|
||||
|
||||
val pathCosts = getPathCosts(grid, start, end)
|
||||
|
||||
// manhattan distance!
|
||||
val pathCostsList = pathCosts.toList()
|
||||
return pathCostsList.sumOf { (pos, cost) ->
|
||||
pathCostsList.asSequence().filter { abs(pos.dc - it.first.dc) + abs(pos.dr - it.first.dr) <= 20 }
|
||||
.filter { (pathCosts[it.first] ?: 0) - (cost + abs(pos.dc - it.first.dc) + abs(pos.dr - it.first.dr)) >= 100 }
|
||||
.map { it.first to (pathCosts[it.first]!! - (cost + it.second)) }.count()
|
||||
}
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day20_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 0)
|
||||
check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2024/Day20")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package aoc2024
|
||||
|
||||
import RelPos
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 21: Keypad Conundrum ---
|
||||
https://adventofcode.com/2024/day/21
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
029A
|
||||
980A
|
||||
179A
|
||||
456A
|
||||
379A
|
||||
"""
|
||||
|
||||
val doorPadMap = hashMapOf(
|
||||
'7' to RelPos(-2, -3),
|
||||
'8' to RelPos(-1, -3),
|
||||
'9' to RelPos(0, -3),
|
||||
'4' to RelPos(-2, -2),
|
||||
'5' to RelPos(-1, -2),
|
||||
'6' to RelPos(0, -2),
|
||||
'1' to RelPos(-2, -1),
|
||||
'2' to RelPos(-1, -1),
|
||||
'3' to RelPos(0, -1),
|
||||
'0' to RelPos(-1, 0),
|
||||
'A' to RelPos(0, 0),
|
||||
)
|
||||
|
||||
val robotPadMap = hashMapOf(
|
||||
'^' to RelPos(-1, 0),
|
||||
'A' to RelPos(0, 0),
|
||||
'<' to RelPos(-2, 1),
|
||||
'v' to RelPos(-1, 1),
|
||||
'>' to RelPos(0, 1),
|
||||
)
|
||||
|
||||
data class PadState(
|
||||
var pos: RelPos,
|
||||
val padMap: Map<Char, RelPos>,
|
||||
val child: PadState? = null,
|
||||
val cache: MutableMap<Pair<RelPos, RelPos>, Long> = HashMap()
|
||||
) {
|
||||
fun getMovement(c: Char): Long {
|
||||
val targetPos = padMap[c]!!
|
||||
return if (child == null) {
|
||||
1
|
||||
} else {
|
||||
val movement = cache.getOrPut(pos to targetPos) {
|
||||
val child = child
|
||||
var sum = 0L
|
||||
if (pos.dr == 0 && targetPos.dc == -2) {
|
||||
while (targetPos.dr < pos.dr) {
|
||||
sum += child.getMovement('^')
|
||||
pos = pos.translate(0, -1)
|
||||
}
|
||||
while (targetPos.dr > pos.dr) {
|
||||
sum += child.getMovement('v')
|
||||
pos = pos.translate(0, 1)
|
||||
}
|
||||
}
|
||||
if (pos.dc == -2 && targetPos.dr == 0) {
|
||||
while (targetPos.dc > pos.dc) {
|
||||
sum += child.getMovement('>')
|
||||
pos = pos.translate(1, 0)
|
||||
}
|
||||
}
|
||||
while (targetPos.dc < pos.dc) {
|
||||
sum += child.getMovement('<')
|
||||
pos = pos.translate(-1, 0)
|
||||
}
|
||||
while (targetPos.dr > pos.dr) {
|
||||
sum += child.getMovement('v')
|
||||
pos = pos.translate(0, 1)
|
||||
}
|
||||
while (targetPos.dr < pos.dr) {
|
||||
sum += child.getMovement('^')
|
||||
pos = pos.translate(0, -1)
|
||||
}
|
||||
while (targetPos.dc > pos.dc) {
|
||||
sum += child.getMovement('>')
|
||||
pos = pos.translate(1, 0)
|
||||
}
|
||||
|
||||
sum += child.getMovement('A')
|
||||
sum
|
||||
}
|
||||
|
||||
pos = targetPos
|
||||
movement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val home = RelPos(0, 0)
|
||||
val myPad = PadState(home, robotPadMap)
|
||||
val robotPad2 = PadState(home, robotPadMap, myPad)
|
||||
val robotPad1 = PadState(home, robotPadMap, robotPad2)
|
||||
val doorPad = PadState(home, doorPadMap, robotPad1)
|
||||
var sum = 0L
|
||||
for (seq in input) {
|
||||
val seqVal = seq.take(3).toLong()
|
||||
sum += seq.sumOf { doorPad.getMovement(it) } * seqVal
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val home = RelPos(0, 0)
|
||||
val myPad = PadState(home, robotPadMap)
|
||||
var lastPad = myPad
|
||||
Array(25) { PadState(home, robotPadMap, lastPad).also { lastPad = it } }
|
||||
val doorPad = PadState(home, doorPadMap, lastPad)
|
||||
var sum = 0L
|
||||
for (seq in input) {
|
||||
val seqVal = seq.take(3).toLong()
|
||||
sum += seq.sumOf { doorPad.getMovement(it) } * seqVal
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day21_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 126384L)
|
||||
//check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2024/Day21")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package aoc2024
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import println
|
||||
import readInput
|
||||
import java.util.*
|
||||
|
||||
/*
|
||||
--- Day 22: Monkey Market ---
|
||||
https://adventofcode.com/2024/day/22
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
1
|
||||
10
|
||||
100
|
||||
2024
|
||||
"""
|
||||
|
||||
val inlineTestInput2 = """
|
||||
1
|
||||
2
|
||||
3
|
||||
2024
|
||||
"""
|
||||
|
||||
fun priceSequence(seed: Int) = generateSequence(seed) {
|
||||
val r1 = (it xor (it shl 6)) and 0xff_ff_ff
|
||||
val r2 = r1 xor (r1 shr 5)
|
||||
(r2 xor (r2 shl 11)) and 0xff_ff_ff
|
||||
}
|
||||
|
||||
fun part1(input: List<String>) =
|
||||
input.map(String::toInt).sumOf { priceSequence(it).elementAt(2000).toLong() }
|
||||
|
||||
fun part2bruteforce(input: List<String>): Int {
|
||||
val strings = input.map(String::toInt).map {
|
||||
val normalString = StringBuffer(2024)
|
||||
val deltaString = StringBuffer(2024)
|
||||
var lastd = it % 10
|
||||
priceSequence(it).take(2001).forEach {
|
||||
val d = it % 10
|
||||
val delta = d - lastd
|
||||
normalString.append(d)
|
||||
deltaString.append('j' + delta)
|
||||
lastd = d
|
||||
}
|
||||
deltaString.toString() to normalString.toString()
|
||||
}
|
||||
|
||||
// it's not my fault that brute force is still working
|
||||
// note that there are less than 19^4 valid combinations, but we don't care
|
||||
return runBlocking {
|
||||
(0..(19 * 19 * 19 * 19)).map {
|
||||
async(Dispatchers.Default) {
|
||||
val token = StringBuffer(4)
|
||||
.append('a' + (it / (19 * 19 * 19)))
|
||||
.append('a' + ((it / (19 * 19)) % 19))
|
||||
.append('a' + ((it / 19) % 19))
|
||||
.append('a' + ((it % 19))).toString()
|
||||
strings.sumOf {
|
||||
val idx = it.first.indexOf(token)
|
||||
if (idx >= 0) it.second[idx + 3].digitToInt() else 0
|
||||
}
|
||||
}
|
||||
}.awaitAll().max()
|
||||
}
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val map = HashMap<Int, MutableList<Int>>()
|
||||
input.map(String::toInt).forEach {
|
||||
var lastd = 0
|
||||
var code = 0
|
||||
var countDown = 4
|
||||
val seenArray = BitSet(19 * 19 * 19 * 19)
|
||||
for (v in priceSequence(it).take(2001)) {
|
||||
val d = v % 10
|
||||
val delta = d - lastd
|
||||
code = ((code * 19) % (19 * 19 * 19 * 19)) + (delta + 9)
|
||||
if (--countDown < 0 && !seenArray[code]) {
|
||||
map.getOrPut(code) { ArrayList() }.add(d)
|
||||
seenArray[code] = true
|
||||
}
|
||||
lastd = d
|
||||
}
|
||||
}
|
||||
return map.maxOf { it.value.sum() }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day22_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput2)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 37327623L)
|
||||
check(testInputPart2Result == 23)
|
||||
|
||||
val input = readInput("aoc2024/Day22")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 23: LAN Party ---
|
||||
https://adventofcode.com/2024/day/23
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
kh-tc
|
||||
qp-kh
|
||||
de-cg
|
||||
ka-co
|
||||
yn-aq
|
||||
qp-ub
|
||||
cg-tb
|
||||
vc-aq
|
||||
tb-ka
|
||||
wh-tc
|
||||
yn-cg
|
||||
kh-ub
|
||||
ta-co
|
||||
de-co
|
||||
tc-td
|
||||
tb-wq
|
||||
wh-td
|
||||
ta-ka
|
||||
td-qp
|
||||
aq-cg
|
||||
wq-ub
|
||||
ub-vc
|
||||
de-ta
|
||||
wq-aq
|
||||
wq-vc
|
||||
wh-yn
|
||||
ka-de
|
||||
kh-ta
|
||||
co-tc
|
||||
wh-qp
|
||||
tb-vc
|
||||
td-yn
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val nodes = HashMap<String, MutableSet<String>>()
|
||||
for (i in input) {
|
||||
val (n1, n2) = i.split("-")
|
||||
val e1 = nodes.getOrPut(n1) { HashSet() }
|
||||
val e2 = nodes.getOrPut(n2) { HashSet() }
|
||||
e1.add(n2)
|
||||
e2.add(n1)
|
||||
}
|
||||
val threesomes = nodes
|
||||
.flatMap {
|
||||
it.value.flatMap { i1 ->
|
||||
it.value.filter { it != i1 }.mapNotNull { i2 ->
|
||||
if (nodes[i1]!!.contains(it.key) && nodes[i2]!!.contains(it.key) && nodes[i1]!!.contains(i2)) sortedSetOf(it.key, i1, i2) else null
|
||||
}
|
||||
}
|
||||
}.map { it.joinToString("-", prefix = "-") }.distinct()
|
||||
|
||||
return threesomes.count { it.contains("-t") }
|
||||
}
|
||||
|
||||
fun bronKerbosch(nodes: Map<String, Set<String>>, r: Set<String>, p: Set<String>, x: MutableSet<String>): Set<String> {
|
||||
if (p.isEmpty() && x.isEmpty()) {
|
||||
return r
|
||||
}
|
||||
var maxClique = emptySet<String>()
|
||||
val ptmp = p.toMutableSet()
|
||||
for (v in p) {
|
||||
val res = bronKerbosch(nodes, r.plus(v), ptmp.intersect(nodes[v]!!), x.intersect(nodes[v]!!).toMutableSet())
|
||||
if (res.size > maxClique.size) {
|
||||
maxClique = res
|
||||
}
|
||||
ptmp.remove(v)
|
||||
x.add(v)
|
||||
}
|
||||
return maxClique
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): String {
|
||||
val nodes = HashMap<String, MutableSet<String>>()
|
||||
for (i in input) {
|
||||
val (n1, n2) = i.split("-")
|
||||
val e1 = nodes.getOrPut(n1) { HashSet() }
|
||||
val e2 = nodes.getOrPut(n2) { HashSet() }
|
||||
e1.add(n2)
|
||||
e2.add(n1)
|
||||
}
|
||||
val largestSet = bronKerbosch(nodes, emptySet(), nodes.keys, mutableSetOf())
|
||||
return largestSet.asSequence().sorted().joinToString(",")
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day23_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 7)
|
||||
check(testInputPart2Result == "co,de,ka,ta")
|
||||
|
||||
val input = readInput("aoc2024/Day23")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
package aoc2024
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayDeque
|
||||
|
||||
/*
|
||||
--- Day 24: Crossed Wires ---
|
||||
https://adventofcode.com/2024/day/24
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
x00: 1
|
||||
x01: 1
|
||||
x02: 1
|
||||
y00: 0
|
||||
y01: 1
|
||||
y02: 0
|
||||
|
||||
x00 AND y00 -> z00
|
||||
x01 XOR y01 -> z01
|
||||
x02 OR y02 -> z02
|
||||
"""
|
||||
|
||||
val inlineTestInput2 = """
|
||||
x00: 1
|
||||
x01: 0
|
||||
x02: 1
|
||||
x03: 1
|
||||
x04: 0
|
||||
y00: 1
|
||||
y01: 1
|
||||
y02: 1
|
||||
y03: 1
|
||||
y04: 1
|
||||
|
||||
ntg XOR fgs -> mjb
|
||||
y02 OR x01 -> tnw
|
||||
kwq OR kpj -> z05
|
||||
x00 OR x03 -> fst
|
||||
tgd XOR rvg -> z01
|
||||
vdt OR tnw -> bfw
|
||||
bfw AND frj -> z10
|
||||
ffh OR nrd -> bqk
|
||||
y00 AND y03 -> djm
|
||||
y03 OR y00 -> psh
|
||||
bqk OR frj -> z08
|
||||
tnw OR fst -> frj
|
||||
gnj AND tgd -> z11
|
||||
bfw XOR mjb -> z00
|
||||
x03 OR x00 -> vdt
|
||||
gnj AND wpb -> z02
|
||||
x04 AND y00 -> kjc
|
||||
djm OR pbm -> qhw
|
||||
nrd AND vdt -> hwm
|
||||
kjc AND fst -> rvg
|
||||
y04 OR y02 -> fgs
|
||||
y01 AND x02 -> pbm
|
||||
ntg OR kjc -> kwq
|
||||
psh XOR fgs -> tgd
|
||||
qhw XOR tgd -> z09
|
||||
pbm OR djm -> kpj
|
||||
x03 XOR y03 -> ffh
|
||||
x00 XOR y04 -> ntg
|
||||
bfw OR bqk -> z06
|
||||
nrd XOR fgs -> wpb
|
||||
frj XOR qhw -> z04
|
||||
bqk OR frj -> z07
|
||||
y03 OR x01 -> nrd
|
||||
hwm AND bqk -> z03
|
||||
tgd XOR rvg -> z12
|
||||
tnw OR pbm -> gnj
|
||||
"""
|
||||
|
||||
data class Node(val n1: String, val n2: String, val op: Int, var target: String, var val1: Boolean? = null, var val2: Boolean? = null) {
|
||||
fun output() = when (op) {
|
||||
0 -> val1!! || val2!!
|
||||
1 -> val1!! && val2!!
|
||||
2 -> val1!! != val2!!
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Node
|
||||
|
||||
return target == other.target
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return target.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
val nodes = ArrayList<Node>()
|
||||
val nodesWaiting = HashMap<String, MutableList<Node>>()
|
||||
var satisfiedNodes = ArrayList<Node>()
|
||||
val zTargets = TreeSet<String>()
|
||||
val zState = HashMap<String, Boolean>()
|
||||
val targetParent = HashMap<String, Node>()
|
||||
val initialStates = HashMap<String, Boolean>()
|
||||
val zInvolvedNodes = HashMap<String, MutableSet<Node>>()
|
||||
val random = kotlin.random.Random(1337)
|
||||
|
||||
fun readCircuit(input: List<String>) {
|
||||
nodes.clear()
|
||||
nodesWaiting.clear()
|
||||
satisfiedNodes.clear()
|
||||
zTargets.clear()
|
||||
zState.clear()
|
||||
targetParent.clear()
|
||||
initialStates.clear()
|
||||
var i = 0
|
||||
while (input[i++].isNotEmpty()) {
|
||||
val (n, st) = input[i - 1].split(": ")
|
||||
initialStates[n] = (st == "1")
|
||||
}
|
||||
while (i < input.size) {
|
||||
val (n1, op, n2, _, target) = input[i++].split(" ")
|
||||
val opCode = when (op) {
|
||||
"OR" -> 0
|
||||
"AND" -> 1
|
||||
"XOR" -> 2
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
if (target.startsWith("z")) {
|
||||
zTargets.add(target)
|
||||
}
|
||||
val node = Node(n1, n2, opCode, target)
|
||||
nodes.add(node)
|
||||
targetParent[target] = node
|
||||
}
|
||||
}
|
||||
|
||||
fun outputState(target: String, output: Boolean, newSatisfiedNodes: MutableCollection<Node>) {
|
||||
if (target.startsWith("z")) {
|
||||
zState[target] = output
|
||||
}
|
||||
val targetList = nodesWaiting[target] ?: return
|
||||
while (targetList.isNotEmpty()) {
|
||||
val tNode = targetList.removeFirst()
|
||||
if (tNode.n1 == target) {
|
||||
tNode.val1 = output
|
||||
if (tNode.val2 != null) {
|
||||
newSatisfiedNodes.add(tNode)
|
||||
}
|
||||
} else {
|
||||
tNode.val2 = output
|
||||
if (tNode.val1 != null) {
|
||||
newSatisfiedNodes.add(tNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun runCircuit(): Boolean {
|
||||
while (zState.size != zTargets.size) {
|
||||
if (satisfiedNodes.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
val newSatisfiedNodes = ArrayList<Node>()
|
||||
for (node in satisfiedNodes) {
|
||||
val output = node.output()
|
||||
node.val1 = null
|
||||
node.val2 = null
|
||||
nodesWaiting.getOrPut(node.n1) { ArrayList() }.add(node)
|
||||
nodesWaiting.getOrPut(node.n2) { ArrayList() }.add(node)
|
||||
|
||||
outputState(node.target, output, newSatisfiedNodes)
|
||||
}
|
||||
satisfiedNodes = newSatisfiedNodes
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
satisfiedNodes.clear()
|
||||
nodesWaiting.clear()
|
||||
zState.clear()
|
||||
for (node in nodes) {
|
||||
node.val1 = null
|
||||
node.val2 = null
|
||||
nodesWaiting.getOrPut(node.n1) { ArrayDeque() }.add(node)
|
||||
nodesWaiting.getOrPut(node.n2) { ArrayDeque() }.add(node)
|
||||
}
|
||||
}
|
||||
|
||||
fun setXY(n: String, value: Long) {
|
||||
var v = value
|
||||
for (i in 0..zTargets.size - 2) {
|
||||
outputState("%s%02d".format(n, i), (v and 1L) != 0L, satisfiedNodes)
|
||||
v = v shr 1
|
||||
}
|
||||
}
|
||||
|
||||
fun readZ(): Long {
|
||||
var result = 0L
|
||||
for (i in zTargets.reversed()) {
|
||||
result *= 2
|
||||
if (zState[i] == true) result++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun calcInvolvedNodes() {
|
||||
zInvolvedNodes.clear()
|
||||
for (target in zTargets) {
|
||||
val set = HashSet<Node>()
|
||||
val queue = ArrayDeque<Node>()
|
||||
queue.add(targetParent[target]!!)
|
||||
while (queue.isNotEmpty()) {
|
||||
val node = queue.removeFirst()
|
||||
set.add(node)
|
||||
val t1 = targetParent[node.n1]
|
||||
val t2 = targetParent[node.n2]
|
||||
if (t1 != null && !set.contains(t1)) queue.add(t1)
|
||||
if (t2 != null && !set.contains(t2)) queue.add(t2)
|
||||
}
|
||||
zInvolvedNodes[target] = set
|
||||
}
|
||||
}
|
||||
|
||||
fun swapOutputs(t1: String, t2: String) {
|
||||
val n1 = targetParent[t1]!!
|
||||
val n2 = targetParent[t2]!!
|
||||
n1.target = t2
|
||||
n2.target = t1
|
||||
targetParent[t1] = n2
|
||||
targetParent[t2] = n1
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
readCircuit(input)
|
||||
reset()
|
||||
initialStates.forEach { outputState(it.key, it.value, satisfiedNodes) }
|
||||
runCircuit()
|
||||
|
||||
return readZ()
|
||||
}
|
||||
|
||||
fun checkIt(x: Long, y: Long, sum: Long, mask: Long = -1L): Boolean {
|
||||
reset()
|
||||
setXY("x", x)
|
||||
setXY("y", y)
|
||||
return runCircuit() && (readZ() and mask) == sum
|
||||
}
|
||||
|
||||
fun countGoodBits(startbit: Int): Int {
|
||||
val inv = (1L shl (zTargets.size - 1)) - 1
|
||||
for (b in startbit..zTargets.size - 2) {
|
||||
val tv = 1L shl b
|
||||
val tvi = (1L shl (b + 1)) - 1L
|
||||
if (!(checkIt(tv, 0L, tv) &&
|
||||
checkIt(tvi, 0L, tvi) &&
|
||||
checkIt(0L, tv, tv) &&
|
||||
checkIt(0L, tvi, tvi) &&
|
||||
checkIt(tv, tv, 2 * tv) &&
|
||||
checkIt(tvi, tvi, 2 * tvi) &&
|
||||
checkIt(inv - tvi, 0L, 0L, tvi) &&
|
||||
checkIt(0L, inv - tvi, 0L, tvi)
|
||||
)
|
||||
) {
|
||||
return b
|
||||
}
|
||||
for (i in 0..(1L shl ((b - 8).coerceIn(1..6)))) {
|
||||
val rx = random.nextLong() and tvi
|
||||
val ry = random.nextLong() and tvi
|
||||
if (!checkIt(rx, ry, rx + ry, mask = tvi * 2 + 1)) return b
|
||||
}
|
||||
}
|
||||
return zTargets.size
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): String {
|
||||
readCircuit(input)
|
||||
calcInvolvedNodes()
|
||||
|
||||
val goodGates = HashSet<Node>()
|
||||
|
||||
val swaps = ArrayList<Pair<String, String>>()
|
||||
|
||||
for (b in 0..zTargets.size - 3) {
|
||||
val zb0 = zInvolvedNodes["z%02d".format(b)]!!
|
||||
val zb1 = zInvolvedNodes["z%02d".format(b + 1)]!!
|
||||
val zb2 = zInvolvedNodes["z%02d".format(b + 2)]!!
|
||||
|
||||
val tv = 1L shl b
|
||||
val tvi = (1L shl (b + 1)) - 1L
|
||||
val good = checkIt(tv, 0L, tv) &&
|
||||
checkIt(tvi, 0L, tvi) &&
|
||||
checkIt(0L, tv, tv) &&
|
||||
checkIt(0L, tvi, tvi) &&
|
||||
checkIt(tv, tv, 2 * tv) &&
|
||||
checkIt(tvi, tvi, 2 * tvi)
|
||||
if (good) {
|
||||
goodGates.addAll(zb0)
|
||||
//goodGates.addAll(zb1)
|
||||
} else {
|
||||
println("Bit error at $b")
|
||||
val badList = zb0.union(zb1).union(zb2).minus(goodGates).map { it.target }.toList()
|
||||
var swapped = false
|
||||
out@ for (p1 in badList.indices) {
|
||||
for (p2 in p1 + 1..badList.lastIndex) {
|
||||
swapOutputs(badList[p1], badList[p2])
|
||||
val goodBits = countGoodBits(b)
|
||||
if (goodBits > b + 1) {
|
||||
println("**** Successfully $goodBits swapped ${badList[p1]} with ${badList[p2]}")
|
||||
swapped = true
|
||||
swaps.add(badList[p1] to badList[p2])
|
||||
break@out
|
||||
} else {
|
||||
swapOutputs(badList[p1], badList[p2])
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!swapped) throw IllegalStateException("Sob!")
|
||||
calcInvolvedNodes()
|
||||
}
|
||||
}
|
||||
return swaps.flatMap { listOf(it.first, it.second) }.sorted().joinToString(",")
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day24_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart1Result2 = part1(testInput2)
|
||||
println("Part 1 Test 2: $testInputPart1Result2")
|
||||
// val testInputPart2Result = part2(testInput2)
|
||||
// println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 4L)
|
||||
check(testInputPart1Result2 == 2024L)
|
||||
//check(testInputPart2Result == "z00,z01,z02,z05")
|
||||
|
||||
val input = readInput("aoc2024/Day24")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package aoc2024
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 25: Code Chronicle ---
|
||||
https://adventofcode.com/2024/day/25
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
#####
|
||||
.####
|
||||
.####
|
||||
.####
|
||||
.#.#.
|
||||
.#...
|
||||
.....
|
||||
|
||||
#####
|
||||
##.##
|
||||
.#.##
|
||||
...##
|
||||
...#.
|
||||
...#.
|
||||
.....
|
||||
|
||||
.....
|
||||
#....
|
||||
#....
|
||||
#...#
|
||||
#.#.#
|
||||
#.###
|
||||
#####
|
||||
|
||||
.....
|
||||
.....
|
||||
#.#..
|
||||
###..
|
||||
###.#
|
||||
###.#
|
||||
#####
|
||||
|
||||
.....
|
||||
.....
|
||||
.....
|
||||
#....
|
||||
#.#..
|
||||
#.#.#
|
||||
#####
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
var p = 0
|
||||
val topRow = IntRange(0, 4)
|
||||
val locks = ArrayList<IntArray>()
|
||||
val keys = ArrayList<IntArray>()
|
||||
while (p < input.size) {
|
||||
val grid = CharGrid(input.subList(p, p + 7))
|
||||
if (topRow.all { grid[it, 0] == '#' }) {
|
||||
val lock = topRow.map { rc -> (topRow.find { grid[rc, it + 1] != '#' } ?: 5) }.toIntArray()
|
||||
locks.add(lock)
|
||||
} else {
|
||||
val key = topRow.map { rc -> 5 - (topRow.find { grid[rc, 5 - it] != '#' } ?: 5) }.toIntArray()
|
||||
keys.add(key)
|
||||
}
|
||||
p += 8
|
||||
}
|
||||
return locks.sumOf { lock -> keys.count { key -> key.zip(lock).all { it.first >= it.second } } }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2024/Day25_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 3)
|
||||
check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2024/Day25")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 1: Secret Entrance ---
|
||||
https://adventofcode.com/2025/day/1
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
L68
|
||||
L30
|
||||
R48
|
||||
L5
|
||||
R60
|
||||
L55
|
||||
L1
|
||||
L99
|
||||
R14
|
||||
L82
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
var dialPos = 50
|
||||
var pass = 0
|
||||
for (i in input) {
|
||||
dialPos = if (i.startsWith("L")) {
|
||||
dialPos + 100 - i.substring(1).toInt()
|
||||
} else {
|
||||
dialPos + i.substring(1).toInt()
|
||||
}
|
||||
dialPos = dialPos % 100
|
||||
if (dialPos == 0) pass++
|
||||
}
|
||||
return pass
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
var dialPos = 50
|
||||
var pass = 0
|
||||
for (i in input) {
|
||||
val rot = i.substring(1).toInt()
|
||||
if (rot == 0) continue
|
||||
if (i.startsWith("L")) {
|
||||
if (dialPos == 0) pass--
|
||||
dialPos -= rot
|
||||
pass += (100 - dialPos) / 100
|
||||
} else {
|
||||
dialPos += rot
|
||||
pass += dialPos / 100
|
||||
}
|
||||
dialPos = if (dialPos < 0) (100 - (-dialPos % 100)) % 100 else dialPos % 100
|
||||
}
|
||||
return pass
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day01_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 3)
|
||||
check(testInputPart2Result == 6)
|
||||
|
||||
val input = readInput("aoc2025/Day01")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import kotlin.math.log10
|
||||
|
||||
/*
|
||||
--- Day 2: Gift Shop ---
|
||||
https://adventofcode.com/2025/day/2
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val ranges = input[0].split(",").map { it.split("-").map { it.toLong() } }
|
||||
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
|
||||
var sum = 0L
|
||||
for ((low, high) in ranges) {
|
||||
var i = low
|
||||
while (i <= high) {
|
||||
val numDigits = log10(i.toDouble()).toInt() + 1
|
||||
if (numDigits % 2 == 1) {
|
||||
i = powTenTable[numDigits]
|
||||
} else {
|
||||
val firstHalf = i / powTenTable[numDigits / 2]
|
||||
val matchNum = firstHalf * powTenTable[numDigits / 2] + firstHalf
|
||||
if (matchNum in low..high) {
|
||||
sum += matchNum
|
||||
}
|
||||
i = (firstHalf + 1) * powTenTable[numDigits / 2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val ranges = input[0].split(",").map { it.split("-").map { it.toLong() } }
|
||||
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
|
||||
val mulElevenArray = arrayOf(
|
||||
generateSequence(1L) { it * 10L + 1L }.take(20).toList().toLongArray(),
|
||||
generateSequence(1L) { it * 100L + 1L }.take(10).toList().toLongArray(),
|
||||
generateSequence(1L) { it * 1000L + 1L }.take(7).toList().toLongArray(),
|
||||
generateSequence(1L) { it * 10000L + 1L }.take(5).toList().toLongArray(),
|
||||
generateSequence(1L) { it * 100000L + 1L }.take(4).toList().toLongArray(),
|
||||
generateSequence(1L) { it * 1000000L + 1L }.take(4).toList().toLongArray(),
|
||||
generateSequence(1L) { it * 10000000L + 1L }.take(3).toList().toLongArray(),
|
||||
generateSequence(1L) { it * 100000000L + 1L }.take(3).toList().toLongArray(),
|
||||
)
|
||||
|
||||
var sum = 0L
|
||||
for ((low, high) in ranges) {
|
||||
var i = low
|
||||
val illegalSet = mutableSetOf<Long>()
|
||||
while (i <= high) {
|
||||
val numDigits = log10(i.toDouble()).toInt() + 1
|
||||
for (n in 1..numDigits / 2) {
|
||||
val firstBit = i / powTenTable[numDigits - n]
|
||||
val matchNum = firstBit * mulElevenArray[n - 1][(numDigits - 1) / n]
|
||||
illegalSet.add(matchNum)
|
||||
}
|
||||
val firstHalf = i / powTenTable[numDigits / 2]
|
||||
i = (firstHalf + 1) * powTenTable[numDigits / 2]
|
||||
}
|
||||
sum += illegalSet.filter { it in low..high }.sum()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day02_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 1227775554L)
|
||||
check(testInputPart2Result == 4174379265L)
|
||||
|
||||
val input = readInput("aoc2025/Day02")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 3: Lobby ---
|
||||
https://adventofcode.com/2025/day/3
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
987654321111111
|
||||
811111111111119
|
||||
234234234234278
|
||||
818181911112111
|
||||
"""
|
||||
|
||||
val powTenTable = generateSequence(1L) { it * 10L }.take(20).toList().toLongArray()
|
||||
|
||||
fun rec(i: String, si: Int, left: Int): Long {
|
||||
for (dig in 9 downTo 1) {
|
||||
val dp = i.indexOf('0' + dig, si)
|
||||
if (dp >= 0) {
|
||||
if (left == 0) {
|
||||
return dig.toLong()
|
||||
} else {
|
||||
val lastDigits = rec(i, dp + 1, left - 1)
|
||||
if (lastDigits >= 0) return lastDigits + dig * powTenTable[left]
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1L
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
return input.sumOf { rec(it, 0, 1) }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
return input.sumOf { rec(it, 0, 11) }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day03_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 357L)
|
||||
check(testInputPart2Result == 3121910778619L)
|
||||
|
||||
val input = readInput("aoc2025/Day03")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package aoc2025
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 4: Printing Department ---
|
||||
https://adventofcode.com/2025/day/4
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
..@@.@@@@.
|
||||
@@@.@.@.@@
|
||||
@@@@@.@.@@
|
||||
@.@@@@..@.
|
||||
@@.@@@@.@@
|
||||
.@@@@@@@.@
|
||||
.@.@.@.@@@
|
||||
@.@@@.@@@@
|
||||
.@@@@@@@@.
|
||||
@.@.@@@.@.
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
val matches = grid.findMatchesRelPos { it == '@' }.filter { grid.matchRelative(it, CharGrid.BOX_POS) { it == '@' }.size < 4 }
|
||||
return matches.count()
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
var removed = 0
|
||||
do {
|
||||
val matches = grid.findMatchesRelPos { it == '@' }.filter { grid.matchRelative(it, CharGrid.BOX_POS) { it == '@' }.size < 4 }
|
||||
grid.applyWithPos(matches) { _, _ -> 'x' }
|
||||
removed += matches.size
|
||||
} while (matches.isNotEmpty())
|
||||
return removed
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day04_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 13)
|
||||
check(testInputPart2Result == 43)
|
||||
|
||||
val input = readInput("aoc2025/Day04")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 5: Cafeteria ---
|
||||
https://adventofcode.com/2025/day/5
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
3-5
|
||||
10-14
|
||||
16-20
|
||||
12-18
|
||||
|
||||
1
|
||||
5
|
||||
8
|
||||
11
|
||||
17
|
||||
32
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val ranges = ArrayList<LongRange>()
|
||||
var freshNum = 0
|
||||
for (i in input) {
|
||||
if (i.contains("-")) {
|
||||
val (low, high) = i.split("-").map { it.toLong() }
|
||||
ranges.add(LongRange(low, high))
|
||||
} else if (i.isNotBlank()) {
|
||||
if (ranges.any { i.toLong() in it }) freshNum++
|
||||
}
|
||||
}
|
||||
return freshNum
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val ranges = ArrayList<LongRange>()
|
||||
for (i in input) {
|
||||
if (i.contains("-")) {
|
||||
val (low, high) = i.split("-").map { it.toLong() }
|
||||
ranges.add(LongRange(low, high))
|
||||
}
|
||||
}
|
||||
|
||||
val consolidated = ArrayList<LongRange>()
|
||||
ranges.sortBy { it.first }
|
||||
var lastLow = ranges[0].first
|
||||
var lastHigh = ranges[0].last
|
||||
for (r in 1..ranges.lastIndex) {
|
||||
if (ranges[r].first > lastHigh) {
|
||||
consolidated.add(LongRange(lastLow, lastHigh))
|
||||
lastLow = ranges[r].first
|
||||
lastHigh = ranges[r].last
|
||||
} else {
|
||||
lastHigh = lastHigh.coerceAtLeast(ranges[r].last)
|
||||
}
|
||||
}
|
||||
consolidated.add(LongRange(lastLow, lastHigh))
|
||||
return consolidated.sumOf { it.last - it.first + 1 }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day05_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 3)
|
||||
check(testInputPart2Result == 14L)
|
||||
|
||||
val input = readInput("aoc2025/Day05")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitLongs
|
||||
|
||||
/*
|
||||
--- Day 6: Trash Compactor ---
|
||||
https://adventofcode.com/2025/day/6
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
123 328 51 64
|
||||
45 64 387 23
|
||||
6 98 215 314
|
||||
* + * +
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val numbers = input.dropLast(1).map { it.splitLongs().toLongArray() }.toList()
|
||||
val ops = input.last().split(" ").filter(String::isNotBlank)
|
||||
return ops.withIndex().sumOf { (c, op) -> if (op == "+") numbers.sumOf { it[c] } else numbers.fold(1L) { acc, row -> acc * row[c] } }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val numbers = ArrayList<LongArray>()
|
||||
val maxLineLength = input.maxOf { it.length }
|
||||
val spaceMask = BooleanArray(maxLineLength)
|
||||
input.forEach { it.withIndex().filter { (_, ch) -> ch != ' ' }.forEach { (c, _) -> spaceMask[c] = true } }
|
||||
val numColl = ArrayList<Long>()
|
||||
for (c in 0 until maxLineLength) {
|
||||
if (spaceMask[c]) numColl.add(input.dropLast(1).map { it[c] }.joinToString("").trim().toLong())
|
||||
if (!spaceMask[c] || c == maxLineLength - 1) {
|
||||
numbers.add(numColl.toLongArray())
|
||||
numColl.clear()
|
||||
}
|
||||
}
|
||||
return input.last().split(" ").filter(String::isNotBlank).withIndex()
|
||||
.sumOf { (c, op) -> if (op == "+") numbers[c].sum() else numbers[c].fold(1L) { acc, row -> acc * row } }
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day06_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 4277556L)
|
||||
check(testInputPart2Result == 3263827L)
|
||||
|
||||
val input = readInput("aoc2025/Day06")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package aoc2025
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 7: Laboratories ---
|
||||
https://adventofcode.com/2025/day/7
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
.......S.......
|
||||
...............
|
||||
.......^.......
|
||||
...............
|
||||
......^.^......
|
||||
...............
|
||||
.....^.^.^.....
|
||||
...............
|
||||
....^.^...^....
|
||||
...............
|
||||
...^.^...^.^...
|
||||
...............
|
||||
..^...^.....^..
|
||||
...............
|
||||
.^.^.^.^.^...^.
|
||||
...............
|
||||
"""
|
||||
|
||||
val memo = ArrayList<HashMap<Int, Long>>()
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val grid = CharGrid(input)
|
||||
var splits = 0
|
||||
for (row in 2 until grid.height step 2) {
|
||||
for (col in 1 until grid.width - 1) {
|
||||
val ch = grid[col, row - 2]
|
||||
if (ch == 'S') {
|
||||
if (grid[col, row] == '^') {
|
||||
grid[col - 1, row] = 'S'
|
||||
grid[col + 1, row] = 'S'
|
||||
splits++
|
||||
} else {
|
||||
grid[col, row] = 'S'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return splits
|
||||
}
|
||||
|
||||
fun rec(grid: CharGrid, col: Int, row: Int): Long {
|
||||
if (row >= grid.height) return 1
|
||||
return memo[row / 2].getOrPut(col) {
|
||||
if (grid[col, row + 2] == '^')
|
||||
rec(grid, col - 1, row + 2) + rec(grid, col + 1, row + 2)
|
||||
else
|
||||
rec(grid, col, row + 2)
|
||||
}
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val grid = CharGrid(input)
|
||||
val col = grid.collectMatches { it == 'S' }.single().second.dc
|
||||
while (memo.size <= grid.height / 2) memo.add(HashMap())
|
||||
return rec(grid, col, 0)
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day07_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 21)
|
||||
check(testInputPart2Result == 40L)
|
||||
|
||||
val input = readInput("aoc2025/Day07")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
import java.util.*
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/*
|
||||
--- Day 8: Playground ---
|
||||
https://adventofcode.com/2025/day/8
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
162,817,812
|
||||
57,618,57
|
||||
906,360,560
|
||||
592,479,940
|
||||
352,342,300
|
||||
466,668,158
|
||||
542,29,236
|
||||
431,825,988
|
||||
739,650,466
|
||||
52,470,668
|
||||
216,146,977
|
||||
819,987,18
|
||||
117,168,530
|
||||
805,96,715
|
||||
346,949,466
|
||||
970,615,88
|
||||
941,993,340
|
||||
862,61,35
|
||||
984,92,344
|
||||
425,690,689
|
||||
"""
|
||||
|
||||
fun dist(n1: IntArray, n2: IntArray) =
|
||||
sqrt(((n1[0] - n2[0]).toFloat() * (n1[0] - n2[0]) + (n1[1] - n2[1]).toFloat() * (n1[1] - n2[1]) + (n1[2] - n2[2]).toFloat() * (n1[2] - n2[2])))
|
||||
|
||||
fun find(parent: IntArray, n: Int): Int {
|
||||
if (parent[n] != n) {
|
||||
parent[n] = find(parent, parent[n])
|
||||
}
|
||||
return parent[n]
|
||||
}
|
||||
|
||||
fun union(parent: IntArray, n1: Int, n2: Int): Boolean {
|
||||
val r1 = find(parent, n1)
|
||||
val r2 = find(parent, n2)
|
||||
if (r1 != r2) {
|
||||
parent[r1] = r2
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// having an oct-tree might speed up things here, so we skip far distances
|
||||
fun createDistances(nodes: Array<IntArray>): PriorityQueue<Pair<Pair<Int, Int>, Float>> {
|
||||
val distances = PriorityQueue<Pair<Pair<Int, Int>, Float>>(nodes.size * nodes.size / 2, Comparator.comparing { it.second })
|
||||
for (i1 in 0 until nodes.size) {
|
||||
for (i2 in i1 + 1 until nodes.size) {
|
||||
val d = dist(nodes[i1], nodes[i2])
|
||||
distances.add((i1 to i2) to d)
|
||||
}
|
||||
}
|
||||
return distances
|
||||
}
|
||||
|
||||
fun part1(input: List<String>, numConnections: Int): Long {
|
||||
val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
|
||||
val distances = createDistances(nodes)
|
||||
|
||||
val n = nodes.size
|
||||
var numCircuits = 0
|
||||
val unionFindParentArray = IntArray(n) { it }
|
||||
while (distances.isNotEmpty()) {
|
||||
val (n1, n2) = distances.remove().first
|
||||
union(unionFindParentArray, n1, n2)
|
||||
numCircuits++
|
||||
if (numCircuits == numConnections) break
|
||||
}
|
||||
|
||||
val circuitSizes = LongArray(n)
|
||||
for (i in 0 until n) {
|
||||
circuitSizes[find(unionFindParentArray, i)]++
|
||||
}
|
||||
return circuitSizes.sortedDescending().take(3).fold(1L) { acc, l -> acc * l }
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
|
||||
val distances = createDistances(nodes)
|
||||
|
||||
val n = nodes.size
|
||||
var numCircuits = 1
|
||||
val unionFindParentArray = IntArray(n) { it }
|
||||
while (distances.isNotEmpty()) {
|
||||
val (n1, n2) = distances.remove().first
|
||||
if (union(unionFindParentArray, n1, n2)) {
|
||||
numCircuits++
|
||||
if (numCircuits == n) {
|
||||
return nodes[n1][0].toLong() * nodes[n2][0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1L
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day08_test")
|
||||
val testInputPart1Result = part1(testInput, 10)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 40L)
|
||||
check(testInputPart2Result == 25272L)
|
||||
|
||||
val input = readInput("aoc2025/Day08")
|
||||
part1(input, 1000).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
/*
|
||||
--- Day 9: Movie Theater ---
|
||||
https://adventofcode.com/2025/day/9
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
7,1
|
||||
11,1
|
||||
11,7
|
||||
9,7
|
||||
9,5
|
||||
2,5
|
||||
2,3
|
||||
7,3
|
||||
"""
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
val coords = input.map { it.splitInts(",") }
|
||||
var maxArea = 0L
|
||||
for (i1 in 0 until coords.size step 2) {
|
||||
for (i2 in i1 + 2 until coords.size step 2) {
|
||||
maxArea =
|
||||
((abs(coords[i1][0] - coords[i2][0]) + 1).toLong() * (abs(coords[i1][1] - coords[i2][1]) + 1).toLong()).coerceAtLeast(
|
||||
maxArea
|
||||
)
|
||||
}
|
||||
}
|
||||
return maxArea
|
||||
}
|
||||
|
||||
fun intersectsRanges(set: TreeMap<Int, ArrayList<IntRange>>, x1: Int, y1: Int, x2: Int, y2: Int) =
|
||||
set.subMap(y1 + 1, y2).values.any { it.any { it.first < x2 && it.last > x1 } }
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val coords = input.map { it.splitInts(",") }
|
||||
val xSpans = TreeMap<Int, ArrayList<IntRange>>()
|
||||
val ySpans = TreeMap<Int, ArrayList<IntRange>>()
|
||||
for (i1 in 0 until coords.size) {
|
||||
val i2 = if (i1 != coords.size - 1) i1 + 1 else 0
|
||||
if (coords[i1][1] == coords[i2][1]) {
|
||||
xSpans.getOrPut(coords[i1][1]) { ArrayList<IntRange>() }.add(
|
||||
IntRange(coords[i1][0].coerceAtMost(coords[i2][0]), coords[i1][0].coerceAtLeast(coords[i2][0]))
|
||||
)
|
||||
} else {
|
||||
ySpans.getOrPut(coords[i1][0]) { ArrayList<IntRange>() }.add(
|
||||
IntRange(coords[i1][1].coerceAtMost(coords[i2][1]), coords[i1][1].coerceAtLeast(coords[i2][1]))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var maxArea = 0L
|
||||
for (i1 in 0 until coords.size step 2) {
|
||||
for (i2 in i1 + 2 until coords.size step 2) {
|
||||
val x1 = coords[i1][0].coerceAtMost(coords[i2][0])
|
||||
val x2 = coords[i1][0].coerceAtLeast(coords[i2][0])
|
||||
val y1 = coords[i1][1].coerceAtMost(coords[i2][1])
|
||||
val y2 = coords[i1][1].coerceAtLeast(coords[i2][1])
|
||||
|
||||
val area = ((x2 - x1 + 1).toLong() * (y2 - y1 + 1).toLong())
|
||||
if (area > maxArea &&
|
||||
!(intersectsRanges(xSpans, x1, y1, x2, y2) || intersectsRanges(ySpans, y1, x1, y2, x2))
|
||||
) {
|
||||
maxArea = area
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxArea
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day09_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 50L)
|
||||
check(testInputPart2Result == 24L)
|
||||
val input = readInput("aoc2025/Day09")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package aoc2025
|
||||
|
||||
import chineseRemainder
|
||||
import extendedGcdBigInteger
|
||||
import primeFactors
|
||||
import primeSequence
|
||||
import println
|
||||
import readInput
|
||||
import sieveOfErastosthenes
|
||||
import splitInts
|
||||
import java.math.BigInteger
|
||||
|
||||
/*
|
||||
--- Day 10: Factory ---
|
||||
https://adventofcode.com/2025/day/10
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
|
||||
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
|
||||
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}
|
||||
"""
|
||||
|
||||
data class Machine(val size: Int, val target: Int, val toggles: IntArray, val joltage: IntArray)
|
||||
|
||||
fun parse(input: List<String>): ArrayList<Machine> {
|
||||
val machines = ArrayList<Machine>()
|
||||
for (i in input) {
|
||||
val stuff = i.split(" ")
|
||||
val machSize = stuff[0].length - 2
|
||||
val target = stuff[0].removeSurrounding("[", "]")
|
||||
.foldIndexed(0) { i, acc, ch -> acc + if (ch == '#') (1 shl i) else 0 }
|
||||
val toggles =
|
||||
stuff.drop(1).dropLast(1)
|
||||
.map { it.removeSurrounding("(", ")").splitInts(",").fold(0) { acc, v -> acc + (1 shl v) } }
|
||||
.toIntArray()
|
||||
val joltages = stuff.last().removeSurrounding("{", "}").splitInts(",").toIntArray()
|
||||
machines.add(Machine(machSize, target, toggles, joltages))
|
||||
}
|
||||
return machines
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
val machines = parse(input)
|
||||
var sumButts = 0
|
||||
for (m in machines) {
|
||||
// look at which different possible sets of toggles need to be pressed to result in the target number
|
||||
// pressing an even time will cancel out the effect, so only look what happens if you press once
|
||||
var minPresses = Int.MAX_VALUE
|
||||
for (tm in 1 until (1 shl m.toggles.size)) {
|
||||
val odds = m.toggles.filterIndexed { i, v -> (1 shl i) and tm != 0 }
|
||||
val result = odds.fold(0) { acc, iv -> acc xor iv }
|
||||
if (result == m.target) {
|
||||
minPresses = minPresses.coerceAtMost(odds.size)
|
||||
}
|
||||
}
|
||||
sumButts += minPresses
|
||||
}
|
||||
return sumButts
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
val machines = parse(input)
|
||||
|
||||
val sieve = sieveOfErastosthenes(10000)
|
||||
val primes = primeSequence(sieve).take(500).toList()
|
||||
var sumButts = 0
|
||||
for (m in machines) {
|
||||
println()
|
||||
val maxJoltage = m.joltage.max()
|
||||
val numBits = 32 - maxJoltage.countLeadingZeroBits()
|
||||
val bigTarget = m.joltage.foldIndexed(BigInteger.ZERO) { index, acc, i ->
|
||||
acc.plus(
|
||||
BigInteger.valueOf(i.toLong()).shiftLeft(numBits * index)
|
||||
)
|
||||
}
|
||||
val bigToggles = ArrayList<BigInteger>()
|
||||
for (t in m.toggles) {
|
||||
var tt = t
|
||||
var bigToggle = BigInteger.ZERO
|
||||
var shift = 0
|
||||
while (tt > 0) {
|
||||
if (tt and 1 != 0) {
|
||||
bigToggle += BigInteger.ONE.shiftLeft(shift)
|
||||
}
|
||||
shift += numBits
|
||||
tt = tt shr 1
|
||||
}
|
||||
bigToggles.add(bigToggle)
|
||||
}
|
||||
|
||||
// j0 * bigtoggle[0] + j1 * bigtoggle[1] + ... = bigTarget
|
||||
// is a Linear Diophantine equation that can be solved with the extended Euclidean algorithm
|
||||
|
||||
val (gcd, coeffients) = extendedGcdBigInteger(bigToggles)
|
||||
if (bigTarget % gcd != BigInteger.ZERO) throw IllegalStateException()
|
||||
|
||||
val factorMap = HashMap<Long, Long>()
|
||||
var good = true
|
||||
val primeFactors = primeFactors(bigTarget.toLong(), sieve)
|
||||
for (pf in primeFactors) {
|
||||
val sf = factorMap[pf]
|
||||
val pr = 1000 % pf
|
||||
if (sf != null && sf != pr) {
|
||||
good = false
|
||||
break
|
||||
}
|
||||
factorMap[pf] = pr
|
||||
}
|
||||
val reducedPairs = factorMap.map { it.value to it.key }.sortedBy { it.second }
|
||||
val rx = reducedPairs.chineseRemainder()
|
||||
|
||||
val leastFingers = 0
|
||||
println(leastFingers)
|
||||
sumButts += leastFingers
|
||||
}
|
||||
return sumButts
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day10_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 7)
|
||||
//check(testInputPart2Result == 33)
|
||||
|
||||
val input = readInput("aoc2025/Day10")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package aoc2025
|
||||
|
||||
import println
|
||||
import readInput
|
||||
|
||||
/*
|
||||
--- Day 11: Reactor ---
|
||||
https://adventofcode.com/2025/day/11
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
aaa: you hhh
|
||||
you: bbb ccc
|
||||
bbb: ddd eee
|
||||
ccc: ddd eee fff
|
||||
ddd: ggg
|
||||
eee: out
|
||||
fff: out
|
||||
ggg: out
|
||||
hhh: ccc fff iii
|
||||
iii: out
|
||||
"""
|
||||
|
||||
val inlineTestInput2 = """
|
||||
svr: aaa bbb
|
||||
aaa: fft
|
||||
fft: ccc
|
||||
bbb: tty
|
||||
tty: ccc
|
||||
ccc: ddd eee
|
||||
ddd: hub
|
||||
hub: fff
|
||||
eee: dac
|
||||
dac: fff
|
||||
fff: ggg hhh
|
||||
ggg: out
|
||||
hhh: out
|
||||
"""
|
||||
|
||||
fun parseInput(input: List<String>): HashMap<String, MutableSet<String>> {
|
||||
val children = HashMap<String, MutableSet<String>>()
|
||||
for (i in input) {
|
||||
val (n, chs) = i.split(": ")
|
||||
val ch = chs.split(" ")
|
||||
children.getOrPut(n) { HashSet() }.addAll(ch)
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
fun rec(
|
||||
children: HashMap<String, MutableSet<String>>,
|
||||
p: String,
|
||||
stop: String,
|
||||
visited: MutableSet<String> = HashSet(),
|
||||
memo: HashMap<String, Long> = HashMap()
|
||||
): Long {
|
||||
if (p == stop) return 1L
|
||||
return memo.getOrPut(p) {
|
||||
visited.add(p)
|
||||
val res = children[p]?.sumOf { rec(children, it, stop, visited, memo) } ?: 0L
|
||||
visited.remove(p)
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
fun part1(input: List<String>): Long {
|
||||
return rec(parseInput(input), "you", "out")
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Long {
|
||||
val children = parseInput(input)
|
||||
|
||||
val srvToDac = rec(children, "svr", "dac")
|
||||
val dacToFft = rec(children, "dac", "fft")
|
||||
val fftToOut = rec(children, "fft", "out")
|
||||
val srvToFft = rec(children, "svr", "fft")
|
||||
val fftToDac = rec(children, "fft", "dac")
|
||||
val dacToOut = rec(children, "dac", "out")
|
||||
|
||||
return srvToDac * dacToFft * fftToOut + srvToFft * fftToDac * dacToOut
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
val testInput2 = inlineTestInput2.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day11_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput2)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
check(testInputPart1Result == 5L)
|
||||
check(testInputPart2Result == 2L)
|
||||
|
||||
val input = readInput("aoc2025/Day11")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package aoc2025
|
||||
|
||||
import CharGrid
|
||||
import println
|
||||
import readInput
|
||||
import splitInts
|
||||
|
||||
/*
|
||||
--- Day 12: Christmas Tree Farm ---
|
||||
https://adventofcode.com/2025/day/12
|
||||
*/
|
||||
fun main() {
|
||||
|
||||
val inlineTestInput = """
|
||||
0:
|
||||
###
|
||||
##.
|
||||
##.
|
||||
|
||||
1:
|
||||
###
|
||||
##.
|
||||
.##
|
||||
|
||||
2:
|
||||
.##
|
||||
###
|
||||
##.
|
||||
|
||||
3:
|
||||
##.
|
||||
###
|
||||
##.
|
||||
|
||||
4:
|
||||
###
|
||||
#..
|
||||
###
|
||||
|
||||
5:
|
||||
###
|
||||
.#.
|
||||
###
|
||||
|
||||
4x4: 0 0 0 0 2 0
|
||||
12x5: 1 0 1 0 2 2
|
||||
12x5: 1 0 1 0 3 2
|
||||
"""
|
||||
|
||||
fun fits(grid: LongArray, x: Int, y: Int, present: Long) =
|
||||
(grid[y] or ((7L and present) shl x) == grid[y]) &&
|
||||
(grid[y + 1] or ((7L and (present shr 3)) shl x) == grid[y + 1]) &&
|
||||
(grid[y + 2] or ((7L and (present shr 6)) shl x) == grid[y + 2])
|
||||
|
||||
fun part1(input: List<String>): Int {
|
||||
var lp = 0
|
||||
val presents = ArrayList<IntArray>()
|
||||
// nothing of this stuff is needed :-(
|
||||
while (!input[lp].contains("x")) {
|
||||
var charGrid = CharGrid(input.subList(lp + 1, lp + 4))
|
||||
lp += 5
|
||||
|
||||
val setRot = HashSet<Int>()
|
||||
for (r in 0..7) {
|
||||
val present = charGrid.generateGridPos().foldIndexed(0) { index, acc, pos -> acc + (if (charGrid[pos] == '#') (1 shl index) else 0) }
|
||||
setRot.add(present)
|
||||
if (r != 3) {
|
||||
// rotate
|
||||
val newGrid = charGrid.copyOf()
|
||||
newGrid.generateGridPos().forEach { (dc, dr) -> newGrid[2 - dr, dc] = charGrid[dc, dr] }
|
||||
charGrid = newGrid
|
||||
} else {
|
||||
// flip
|
||||
val newGrid = charGrid.copyOf()
|
||||
newGrid.generateGridPos().forEach { (dc, dr) -> newGrid[2 - dc, dr] = charGrid[dc, dr] }
|
||||
charGrid = newGrid
|
||||
}
|
||||
}
|
||||
presents.add(setRot.toIntArray())
|
||||
}
|
||||
|
||||
var fitted = 0
|
||||
val presentSizes = presents.map { it[0].countOneBits() }.toIntArray()
|
||||
|
||||
for (p in lp until input.size) {
|
||||
val (dim, pl) = input[p].split(": ")
|
||||
val (width, height) = dim.splitInts("x")
|
||||
val placements = pl.splitInts().toIntArray()
|
||||
val totalSize = placements.mapIndexed { i, v -> v * presentSizes[i] }.sum()
|
||||
if (totalSize > width * height) {
|
||||
continue
|
||||
}
|
||||
|
||||
val totalPresents = placements.sum()
|
||||
// just assume it will fit if there is enough area
|
||||
if (totalPresents * 9 <= width * height) {
|
||||
fitted++
|
||||
continue
|
||||
}
|
||||
|
||||
println("Oh no!")
|
||||
// here the hard part would have started, but except for the example input,
|
||||
// it never gets here
|
||||
//val grid = LongArray(height) { (1L shl width) - 1L }
|
||||
}
|
||||
return fitted
|
||||
}
|
||||
|
||||
fun part2(input: List<String>): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// test if implementation meets criteria from the description, like:
|
||||
val testInput = inlineTestInput.trim().reader().readLines()
|
||||
//val testInput = readInput("aoc2025/Day12_test")
|
||||
val testInputPart1Result = part1(testInput)
|
||||
println("Part 1 Test: $testInputPart1Result")
|
||||
val testInputPart2Result = part2(testInput)
|
||||
println("Part 2 Test: $testInputPart2Result")
|
||||
//check(testInputPart1Result == 2)
|
||||
check(testInputPart2Result == 0)
|
||||
|
||||
val input = readInput("aoc2025/Day12")
|
||||
part1(input).println()
|
||||
part2(input).println()
|
||||
}
|
||||
@@ -4,30 +4,30 @@ import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlHandler
|
||||
import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser
|
||||
import fuel.Fuel
|
||||
import fuel.method
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.Month
|
||||
import java.time.ZoneId
|
||||
import java.util.*
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
fun main() {
|
||||
var cookie = "<insert your session cookie here or store in gradle.properties>"
|
||||
|
||||
if (Files.exists(Paths.get("gradle.properties"))) {
|
||||
val cookieFromProps = Files.readAllLines(Paths.get("gradle.properties"))
|
||||
.map { it.split("=") }
|
||||
.filter { it.size == 2 }
|
||||
.filter { it[0] == "cookie" }
|
||||
.map { it[1] }.firstOrNull()
|
||||
if (cookieFromProps != null) {
|
||||
cookie = cookieFromProps
|
||||
}
|
||||
val props = Properties()
|
||||
props.load(Files.newBufferedReader(Paths.get("gradle.properties")))
|
||||
cookie = props.getProperty("cookie", cookie)
|
||||
}
|
||||
|
||||
val downloader = Downloader(2023, "aoc2023", cookie)
|
||||
val downloader = Downloader(2025, "aoc2025", cookie)
|
||||
downloader.updateToLatest()
|
||||
}
|
||||
|
||||
@@ -46,15 +46,16 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
|
||||
if (!Files.exists(targetDir)) {
|
||||
Files.createDirectories(targetDir)
|
||||
}
|
||||
val now = LocalDate.now(ZoneId.of("UTC-1"))
|
||||
val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, 25))) {
|
||||
val now = LocalDate.now(ZoneId.of("UTC+1"))
|
||||
val maxPuzzles = if (year < 2025) 25 else 12
|
||||
val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, maxPuzzles))) {
|
||||
if (now.isAfter(LocalDate.of(year, Month.NOVEMBER, 30))) {
|
||||
now.dayOfMonth
|
||||
} else {
|
||||
throw IllegalStateException("You're too early.")
|
||||
}
|
||||
} else {
|
||||
25
|
||||
maxPuzzles
|
||||
}
|
||||
|
||||
for (day in 1..lastDay) {
|
||||
@@ -63,6 +64,20 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
|
||||
val descriptionFile = targetDir.resolve(DESC_FILENAME.format(day))
|
||||
val genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day))
|
||||
if (!Files.exists(inputFile)) {
|
||||
if (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour < 5) {
|
||||
println("Puzzle will not be available within the next hour. Skipping.")
|
||||
break
|
||||
}
|
||||
while (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour == 5) {
|
||||
println("Waiting for puzzle to become available...")
|
||||
runBlocking {
|
||||
if (LocalTime.now(ZoneId.of("UTC+1")).minute < 59) {
|
||||
delay(1.minutes)
|
||||
} else {
|
||||
delay(1.seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
println("Attempting to download input for day $day")
|
||||
val (code, data) = downloadInput(day)
|
||||
if (code != 200) {
|
||||
@@ -97,7 +112,13 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
|
||||
.onOpenTag { name, attributes, isImplied ->
|
||||
when (name) {
|
||||
"article" -> enableDescription = (attributes["class"] == "day-desc")
|
||||
"pre" -> preFound = enableDescription
|
||||
"pre" -> {
|
||||
if (enableDescription && testInput.isNotEmpty()) {
|
||||
testInput += "---<here be dragons>---\n"
|
||||
}
|
||||
preFound = enableDescription
|
||||
}
|
||||
|
||||
"code" -> codeFound = preFound
|
||||
}
|
||||
}
|
||||
@@ -133,7 +154,8 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
|
||||
template = template.replace("\${package}", packageName)
|
||||
template = template.replace("\${day}", "%02d".format(day))
|
||||
template = template.replace("\${testinput}", testInput)
|
||||
template = template.replace("\${description}", description)
|
||||
template = template.replace("\${description}", description.substringBefore("\n"))
|
||||
template = template.replace("\${weblink}", "https://adventofcode.com/$year/day/$day")
|
||||
Files.writeString(genClassFile, template)
|
||||
}
|
||||
}
|
||||
@@ -153,7 +175,7 @@ class Downloader(val year: Int, val packageName: String, val sessionCookie: Stri
|
||||
url = "https://adventofcode.com/$year/day/$day$suffix",
|
||||
method = "GET",
|
||||
headers = mapOf(
|
||||
"User-Agent" to "git.platon42.de/chrisly42/advent-of-code-2023",
|
||||
"User-Agent" to "git.platon42.de/chrisly42/advent-of-code (chrisly@platon42.de)",
|
||||
"Cookie" to "session=$sessionCookie"
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user