aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortchojnacki <tomaszchojnacki2001@gmail.com>2022-08-10 21:01:47 +0200
committertchojnacki <tomaszchojnacki2001@gmail.com>2022-08-10 21:01:47 +0200
commit5ef64073bbb183dd425ffa360d080ca58a1c08e1 (patch)
tree9a108a0ed6d0ee7bb6799dc2260cebef2fb96723
parent5a4e32c427862238fd092cbc28be4622d1552a72 (diff)
downloadgleam_aoc2020-5ef64073bbb183dd425ffa360d080ca58a1c08e1.tar.gz
gleam_aoc2020-5ef64073bbb183dd425ffa360d080ca58a1c08e1.zip
Refactor all days to provide better encapsulation
-rw-r--r--src/Day01.kt16
-rw-r--r--src/Day02.kt34
-rw-r--r--src/Day03.kt81
-rw-r--r--src/Day04.kt124
-rw-r--r--src/Day05.kt61
-rw-r--r--src/Day06.kt50
-rw-r--r--src/Day07.kt12
-rw-r--r--src/Day08.kt13
-rw-r--r--src/Day09.kt19
-rw-r--r--src/Day10.kt115
-rw-r--r--src/Day11.kt74
-rw-r--r--src/Day12.kt100
-rw-r--r--src/Day13.kt109
-rw-r--r--src/Day14.kt61
-rw-r--r--src/Day15.kt159
-rw-r--r--src/Day16.kt218
-rw-r--r--src/Day17.kt78
-rw-r--r--src/Day18.kt175
-rw-r--r--src/Day24.kt23
-rw-r--r--src/Day25.kt136
-rw-r--r--src/Utils.kt20
21 files changed, 820 insertions, 858 deletions
diff --git a/src/Day01.kt b/src/Day01.kt
index 6d45527..4ac1ef7 100644
--- a/src/Day01.kt
+++ b/src/Day01.kt
@@ -1,22 +1,24 @@
-fun main() {
- fun part1(input: List<Int>): Int =
+object Day01 {
+ fun part1(input: List<Int>) =
input
.zipWithNext()
.count { it.second > it.first }
- fun part2(input: List<Int>): Int =
+ fun part2(input: List<Int>) =
input
.asSequence()
.windowed(3)
.map { it.sum() }
.zipWithNext()
.count { it.second > it.first }
+}
+fun main() {
val testInput = readInputAsNumbers("Day01_test")
- check(part1(testInput) == 7)
- check(part2(testInput) == 5)
+ check(Day01.part1(testInput) == 7)
+ check(Day01.part2(testInput) == 5)
val input = readInputAsNumbers("Day01")
- println(part1(input))
- println(part2(input))
+ println(Day01.part1(input))
+ println(Day01.part2(input))
}
diff --git a/src/Day02.kt b/src/Day02.kt
index 401c508..2eb085a 100644
--- a/src/Day02.kt
+++ b/src/Day02.kt
@@ -1,19 +1,14 @@
-/**
- * Given a list of correct command strings, dispatch [action] taking command name and argument on each of them.
- * @param commands list of valid command strings
- * @param action function taking a string (command name) and integer (argument) that gets called for each command
- */
-fun dispatchCommands(commands: List<String>, action: (command: String, argument: Int) -> Unit) {
- for (line in commands) {
- val parts = line.split(" ")
- val command = parts[0]
- val argument = parts[1].toInt()
-
- action(command, argument)
+object Day02 {
+ private fun dispatchCommands(commands: List<String>, action: (command: String, argument: Int) -> Unit) {
+ for (line in commands) {
+ val parts = line.split(" ")
+ val command = parts[0]
+ val argument = parts[1].toInt()
+
+ action(command, argument)
+ }
}
-}
-fun main() {
fun part1(input: List<String>): Int {
var horizontal = 0
var depth = 0
@@ -40,6 +35,7 @@ fun main() {
horizontal += argument
depth += aim * argument
}
+
"down" -> aim += argument
"up" -> aim -= argument
}
@@ -47,12 +43,14 @@ fun main() {
return horizontal * depth
}
+}
+fun main() {
val testInput = readInputAsLines("Day02_test")
- check(part1(testInput) == 150)
- check(part2(testInput) == 900)
+ check(Day02.part1(testInput) == 150)
+ check(Day02.part2(testInput) == 900)
val input = readInputAsLines("Day02")
- println(part1(input))
- println(part2(input))
+ println(Day02.part1(input))
+ println(Day02.part2(input))
}
diff --git a/src/Day03.kt b/src/Day03.kt
index 94b1bf4..1418a2e 100644
--- a/src/Day03.kt
+++ b/src/Day03.kt
@@ -1,61 +1,30 @@
-/**
- * Add two lists element by element.
- * @param l1 first list
- * @param l2 second list
- * @return a new list, where each element at index `i` is equal to `l1.get(i) + l2.get(i)`
- */
-fun addLists(l1: List<Int>, l2: List<Int>): List<Int> = l1.zip(l2).map { it.first + it.second }
+object Day03 {
+ private fun addLists(l1: List<Int>, l2: List<Int>) = l1.zip(l2).map { it.first + it.second }
-/**
- * Returns the decimal value of a binary number represented as a list of bits.
- * @param bitList list of bits
- * @return decimal value of binary number
- */
-fun valueOfBits(bitList: List<Int>): Int = bitList.reduce { acc, bit -> acc * 2 + bit }
+ private fun valueOfBits(bitList: List<Int>) = bitList.reduce { acc, bit -> acc * 2 + bit }
-/**
- * Invert all bits of a binary number.
- * @param bitList list of bits
- * @return list of bits, where each bit of [bitList] is flipped
- */
-fun invertBits(bitList: List<Int>): List<Int> = bitList.map { bit -> 1 - bit }
+ private fun invertBits(bitList: List<Int>) = bitList.map { bit -> 1 - bit }
-/**
- * Given a list of binary numbers (represented as a list of bits) return a binary number,
- * where each bit has the value most common at that position among the numbers in the list.
- * @param input list of lists of bits
- * @return list of bits, where each bit has the most common value in the corresponding position of all numbers in the list
- */
-fun mostCommonBits(input: List<List<Int>>): List<Int> =
- input
- .reduce(::addLists)
- .map { bit -> if (bit.toDouble() / input.size >= 0.5) 1 else 0 }
+ private fun mostCommonBits(input: List<List<Int>>): List<Int> =
+ input
+ .reduce(::addLists)
+ .map { bit -> if (bit.toDouble() / input.size >= 0.5) 1 else 0 }
-/**
- * Find a binary number from a list, by filtering out values until one remains.
- * While the list has more than one number, remove numbers having a different bit
- * value at current position (starting at 0, increased by 1 after each filtering)
- * then the value at corresponding position in the `comparisonCriteria(currentList)` list.
- * @param input list used to search for the value
- * @param comparisonCriteria function returning a binary number used to compare with others for filtering
- * @return found binary number represented as a list of bits
- */
-fun selectByBitCriteria(
- input: List<List<Int>>,
- comparisonCriteria: (currentList: List<List<Int>>) -> List<Int>
-): List<Int>? {
- var list = input
- var index = 0
+ private fun selectByBitCriteria(
+ input: List<List<Int>>,
+ comparisonCriteria: (currentList: List<List<Int>>) -> List<Int>
+ ): List<Int>? {
+ var list = input
+ var index = 0
- while (list.size > 1 && index < list[0].size) {
- list = list.filter { e -> e[index] == comparisonCriteria(list)[index] }
- index += 1
- }
+ while (list.size > 1 && index < list[0].size) {
+ list = list.filter { e -> e[index] == comparisonCriteria(list)[index] }
+ index += 1
+ }
- return list.getOrNull(0)
-}
+ return list.getOrNull(0)
+ }
-fun main() {
fun part1(input: List<List<Int>>): Int =
mostCommonBits(input)
.let { gammaBits ->
@@ -69,12 +38,14 @@ fun main() {
return valueOfBits(oxygenRating) * valueOfBits(scrubberRating)
}
+}
+fun main() {
val testInput = readInputAsBitLists("Day03_test")
- check(part1(testInput) == 198)
- check(part2(testInput) == 230)
+ check(Day03.part1(testInput) == 198)
+ check(Day03.part2(testInput) == 230)
val input = readInputAsBitLists("Day03")
- println(part1(input))
- println(part2(input))
+ println(Day03.part1(input))
+ println(Day03.part2(input))
}
diff --git a/src/Day04.kt b/src/Day04.kt
index 4f93a67..96cdf3b 100644
--- a/src/Day04.kt
+++ b/src/Day04.kt
@@ -1,95 +1,97 @@
-class Bingo(private val revealQueue: ArrayDeque<Int>, private val boards: List<Board>) {
- companion object {
- fun fromString(input: String): Bingo {
- val sections = input.trim().split("(\\r?\\n){2}".toRegex())
+object Day04 {
+ private class Bingo(private val revealQueue: ArrayDeque<Int>, private val boards: List<Board>) {
+ companion object {
+ fun fromString(input: String): Bingo {
+ val sections = input.trim().split("(\\r?\\n){2}".toRegex())
- val revealQueueString = sections.first()
- val revealQueue = ArrayDeque(revealQueueString.split(",").map(String::toInt))
+ val revealQueueString = sections.first()
+ val revealQueue = ArrayDeque(revealQueueString.split(",").map(String::toInt))
- val boards = sections.drop(1).map { Board.fromMatrixString(it) }
+ val boards = sections.drop(1).map { Board.fromMatrixString(it) }
- return Bingo(revealQueue, boards)
+ return Bingo(revealQueue, boards)
+ }
}
- }
- fun getResults() = sequence {
- while (revealQueue.isNotEmpty() && boards.any { !it.didWin }) {
- val number = revealQueue.removeFirst()
+ fun getResults() = sequence {
+ while (revealQueue.isNotEmpty() && boards.any { !it.didWin }) {
+ val number = revealQueue.removeFirst()
- for (board in boards.filter { !it.didWin }) {
- board.reveal(number)
+ for (board in boards.filter { !it.didWin }) {
+ board.reveal(number)
- if (board.didWin) {
- yield(board.sumOfUnmarked * number)
+ if (board.didWin) {
+ yield(board.sumOfUnmarked * number)
+ }
}
}
}
- }
- class Board(private var rows: List<List<Square>>) {
- companion object {
- fun fromMatrixString(matrixString: String): Board = Board(
- matrixString.trim().split("\\r?\\n".toRegex()).map { rowString ->
- rowString.trim().split("\\s+".toRegex()).map { squareString ->
- Square.Unmarked(squareString.toInt())
+ private class Board(private var rows: List<List<Square>>) {
+ companion object {
+ fun fromMatrixString(matrixString: String): Board = Board(
+ matrixString.trim().split("\\r?\\n".toRegex()).map { rowString ->
+ rowString.trim().split("\\s+".toRegex()).map { squareString ->
+ Square.Unmarked(squareString.toInt())
+ }
}
- }
- )
+ )
- private const val SIZE = 5
+ private const val SIZE = 5
- private val ROWS = (0 until SIZE).map { row ->
- (0 until SIZE).map { square ->
- Pair(row, square)
+ private val ROWS = (0 until SIZE).map { row ->
+ (0 until SIZE).map { square ->
+ Pair(row, square)
+ }
}
- }
- private val COLUMNS = (0 until SIZE).map { column ->
- (0 until SIZE).map { square ->
- Pair(square, column)
+ private val COLUMNS = (0 until SIZE).map { column ->
+ (0 until SIZE).map { square ->
+ Pair(square, column)
+ }
}
- }
- private val WINNING_CONFIGURATIONS = ROWS + COLUMNS
- }
+ private val WINNING_CONFIGURATIONS = ROWS + COLUMNS
+ }
- fun reveal(number: Int) {
- rows = rows.map { row ->
- row.map { square ->
- if (square is Square.Unmarked && square.number == number) Square.Marked else square
+ fun reveal(number: Int) {
+ rows = rows.map { row ->
+ row.map { square ->
+ if (square is Square.Unmarked && square.number == number) Square.Marked else square
+ }
}
}
- }
- val didWin: Boolean
- get() = WINNING_CONFIGURATIONS.any { configuration ->
- configuration.all { (row, column) -> rows[row][column] is Square.Marked }
- }
+ val didWin: Boolean
+ get() = WINNING_CONFIGURATIONS.any { configuration ->
+ configuration.all { (row, column) -> rows[row][column] is Square.Marked }
+ }
- val sumOfUnmarked: Int
- get() = rows.fold(0) { racc, row ->
- racc + row.fold(0) { sacc, square ->
- sacc + if (square is Square.Unmarked) square.number else 0
+ val sumOfUnmarked: Int
+ get() = rows.fold(0) { rowAcc, row ->
+ rowAcc + row.fold(0) { squareAcc, square ->
+ squareAcc + if (square is Square.Unmarked) square.number else 0
+ }
}
- }
- sealed class Square {
- object Marked : Square()
- class Unmarked(val number: Int) : Square()
+ sealed class Square {
+ object Marked : Square()
+ class Unmarked(val number: Int) : Square()
+ }
}
}
+
+ fun bothParts(input: String) = Bingo.fromString(input).getResults().let { it.first() to it.last() }
}
fun main() {
- fun part1(input: String): Int = Bingo.fromString(input).getResults().first()
-
- fun part2(input: String): Int = Bingo.fromString(input).getResults().last()
-
val testInput = readInputAsString("Day04_test")
- check(part1(testInput) == 4512)
- check(part2(testInput) == 1924)
+ val testOutput = Day04.bothParts(testInput)
+ check(testOutput.first == 4512)
+ check(testOutput.second == 1924)
val input = readInputAsString("Day04")
- println(part1(input))
- println(part2(input))
+ val output = Day04.bothParts(input)
+ println(output.first)
+ println(output.second)
}
diff --git a/src/Day05.kt b/src/Day05.kt
index eaa7b66..2c5e219 100644
--- a/src/Day05.kt
+++ b/src/Day05.kt
@@ -2,40 +2,40 @@ import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.sign
-data class Line(val start: Pos2D, val end: Pos2D) {
- companion object {
- fun fromString(input: String): Line {
- val (start, end) = input.split(" -> ").map { coordinateString ->
- val (x, y) = coordinateString.split(",").map(String::toInt)
- Pos2D(x, y)
+object Day05 {
+ private data class Line(val start: Pos2D, val end: Pos2D) {
+ companion object {
+ fun fromString(input: String): Line {
+ val (start, end) = input.split(" -> ").map { coordinateString ->
+ val (x, y) = coordinateString.split(",").map(String::toInt)
+ Pos2D(x, y)
+ }
+
+ return Line(start, end)
}
-
- return Line(start, end)
}
- }
- val isHorizontalOrVertical: Boolean
- get() = start.x == end.x || start.y == end.y
+ val isHorizontalOrVertical: Boolean
+ get() = start.x == end.x || start.y == end.y
- val isDiagonal: Boolean
- get() = (end.x - start.x).absoluteValue == (end.y - start.y).absoluteValue
+ val isDiagonal: Boolean
+ get() = (end.x - start.x).absoluteValue == (end.y - start.y).absoluteValue
- val pointSequence: Sequence<Pos2D>
- get() = sequence {
- val xOffset = end.x - start.x
- val yOffset = end.y - start.y
+ val pointSequence: Sequence<Pos2D>
+ get() = sequence {
+ val xOffset = end.x - start.x
+ val yOffset = end.y - start.y
- for (s in 0..max(xOffset.absoluteValue, yOffset.absoluteValue)) {
- val x = start.x + s * xOffset.sign
- val y = start.y + s * yOffset.sign
+ for (s in 0..max(xOffset.absoluteValue, yOffset.absoluteValue)) {
+ val x = start.x + s * xOffset.sign
+ val y = start.y + s * yOffset.sign
- yield(Pos2D(x, y))
+ yield(Pos2D(x, y))
+ }
}
- }
-}
+ }
-fun main() {
- fun helper(input: List<String>, linePredicate: (line: Line) -> Boolean) = input
+ private fun helper(input: List<String>, linePredicate: (line: Line) -> Boolean) = input
.asSequence()
.map(Line::fromString)
.filter(linePredicate)
@@ -49,11 +49,14 @@ fun main() {
fun part2(input: List<String>): Int = helper(input) { it.isHorizontalOrVertical || it.isDiagonal }
+}
+
+fun main() {
val testInput = readInputAsLines("Day05_test")
- check(part1(testInput) == 5)
- check(part2(testInput) == 12)
+ check(Day05.part1(testInput) == 5)
+ check(Day05.part2(testInput) == 12)
val input = readInputAsLines("Day05")
- println(part1(input))
- println(part2(input))
+ println(Day05.part1(input))
+ println(Day05.part2(input))
}
diff --git a/src/Day06.kt b/src/Day06.kt
index 57e56f2..4627cca 100644
--- a/src/Day06.kt
+++ b/src/Day06.kt
@@ -1,36 +1,38 @@
-fun calculateFishPopulation(input: String, days: Int): Long {
- val fishCounts =
- input
- .trim()
- .split(",")
- .map(String::toInt)
- .groupingBy { it }
- .eachCount()
- .mapValues { (_, v) -> v.toLong() }
- .toMutableMap()
+object Day06 {
+ private fun calculateFishPopulation(input: String, days: Int): Long {
+ val fishCounts =
+ input
+ .trim()
+ .split(",")
+ .map(String::toInt)
+ .groupingBy { it }
+ .eachCount()
+ .mapValues { (_, v) -> v.toLong() }
+ .toMutableMap()
- repeat(days) {
- val readyToBirth = fishCounts.getOrDefault(0, 0)
- repeat(8) {
- fishCounts[it] = fishCounts.getOrDefault(it + 1, 0)
+ repeat(days) {
+ val readyToBirth = fishCounts.getOrDefault(0, 0)
+ repeat(8) {
+ fishCounts[it] = fishCounts.getOrDefault(it + 1, 0)
+ }
+ fishCounts.merge(6, readyToBirth, Long::plus)
+ fishCounts[8] = readyToBirth
}
- fishCounts.merge(6, readyToBirth, Long::plus)
- fishCounts[8] = readyToBirth
- }
- return fishCounts.values.sum()
-}
+ return fishCounts.values.sum()
+ }
-fun main() {
fun part1(input: String): Int = calculateFishPopulation(input, 80).toInt()
fun part2(input: String): Long = calculateFishPopulation(input, 256)
+}
+fun main() {
val testInput = readInputAsString("Day06_test")
- check(part1(testInput) == 5934)
- check(part2(testInput) == 26984457539)
+ check(Day06.part1(testInput) == 5934)
+ check(Day06.part2(testInput) == 26984457539)
val input = readInputAsString("Day06")
- println(part1(input))
- println(part2(input))
+ println(Day06.part1(input))
+ println(Day06.part2(input))
}
diff --git a/src/Day07.kt b/src/Day07.kt
index 204dca4..9c1b79f 100644
--- a/src/Day07.kt
+++ b/src/Day07.kt
@@ -1,6 +1,6 @@
import kotlin.math.absoluteValue
-fun main() {
+object Day07 {
fun part1(input: String): Int {
val numbers = input.trim().split(",").map(String::toInt)
val range = numbers.minOrNull()!!..numbers.maxOrNull()!!
@@ -14,12 +14,14 @@ fun main() {
return range.minOf { n -> numbers.map { (it - n).absoluteValue }.sumOf { (it * (it + 1)) / 2 } }
}
+}
+fun main() {
val testInput = readInputAsString("Day07_test")
- check(part1(testInput) == 37)
- check(part2(testInput) == 168)
+ check(Day07.part1(testInput) == 37)
+ check(Day07.part2(testInput) == 168)
val input = readInputAsString("Day07")
- println(part1(input))
- println(part2(input))
+ println(Day07.part1(input))
+ println(Day07.part2(input))
}
diff --git a/src/Day08.kt b/src/Day08.kt
index d0f050e..b513352 100644
--- a/src/Day08.kt
+++ b/src/Day08.kt
@@ -1,4 +1,4 @@
-fun main() {
+object Day08 {
fun part1(input: List<String>): Int =
input.sumOf {
it
@@ -38,13 +38,14 @@ fun main() {
.joinToString("") { encodings.indexOf(it.toSet()).toString() }
.toLong()
}
+}
-
+fun main() {
val testInput = readInputAsLines("Day08_test")
- check(part1(testInput) == 26)
- check(part2(testInput) == 61229L)
+ check(Day08.part1(testInput) == 26)
+ check(Day08.part2(testInput) == 61229L)
val input = readInputAsLines("Day08")
- println(part1(input))
- println(part2(input))
+ println(Day08.part1(input))
+ println(Day08.part2(input))
}
diff --git a/src/Day09.kt b/src/Day09.kt
index 9fef398..9d0c41d 100644
--- a/src/Day09.kt
+++ b/src/Day09.kt
@@ -1,8 +1,8 @@
-fun Map<Pos2D, Int>.getLowPoints(): Map<Pos2D, Int> =
- filter { (pos, num) -> Pos2D.directions4.all { num < getOrDefault(pos + it, 9) } }
+object Day09 {
+ private fun Map<Pos2D, Int>.getLowPoints() =
+ filter { (pos, num) -> Pos2D.directions4.all { num < getOrDefault(pos + it, 9) } }
-fun main() {
- fun part1(input: List<String>): Int =
+ fun part1(input: List<String>) =
parseToMap(input).getLowPoints().values.sumOf { it + 1 }
fun part2(input: List<String>): Int {
@@ -27,13 +27,14 @@ fun main() {
.take(3)
.sum()
}
+}
-
+fun main() {
val testInput = readInputAsLines("Day09_test")
- check(part1(testInput) == 15)
- check(part2(testInput) == 1134)
+ check(Day09.part1(testInput) == 15)
+ check(Day09.part2(testInput) == 1134)
val input = readInputAsLines("Day09")
- println(part1(input))
- println(part2(input))
+ println(Day09.part1(input))
+ println(Day09.part2(input))
}
diff --git a/src/Day10.kt b/src/Day10.kt
index 8f73f2f..d6d026c 100644
--- a/src/Day10.kt
+++ b/src/Day10.kt
@@ -1,72 +1,74 @@
-enum class ChunkDelimiter(val open: Char, val close: Char) {
- Parentheses('(', ')'),
- Brackets('[', ']'),
- Braces('{', '}'),
- Angled('<', '>');
+object Day10 {
+ private enum class ChunkDelimiter(val open: Char, val close: Char) {
+ Parentheses('(', ')'),
+ Brackets('[', ']'),
+ Braces('{', '}'),
+ Angled('<', '>');
- companion object {
- fun isOpening(character: Char): Boolean = values().any { it.open == character }
- fun isClosing(character: Char): Boolean = values().any { it.close == character }
- fun from(character: Char): ChunkDelimiter = values().find { it.open == character || it.close == character }!!
+ companion object {
+ fun isOpening(character: Char): Boolean = values().any { it.open == character }
+ fun isClosing(character: Char): Boolean = values().any { it.close == character }
+ fun from(character: Char): ChunkDelimiter =
+ values().find { it.open == character || it.close == character }!!
+ }
}
-}
-sealed class SyntaxError {
- object None : SyntaxError()
- class IncompleteLine(private val stack: ArrayDeque<ChunkDelimiter>) : SyntaxError() {
- val score: Long
- get() {
- fun delimiterValue(delimiter: ChunkDelimiter): Int = when (delimiter) {
- ChunkDelimiter.Parentheses -> 1
- ChunkDelimiter.Brackets -> 2
- ChunkDelimiter.Braces -> 3
- ChunkDelimiter.Angled -> 4
+ private sealed class SyntaxError {
+ object None : SyntaxError()
+ class IncompleteLine(private val stack: ArrayDeque<ChunkDelimiter>) : SyntaxError() {
+ val score: Long
+ get() {
+ fun delimiterValue(delimiter: ChunkDelimiter): Int = when (delimiter) {
+ ChunkDelimiter.Parentheses -> 1
+ ChunkDelimiter.Brackets -> 2
+ ChunkDelimiter.Braces -> 3
+ ChunkDelimiter.Angled -> 4
+ }
+
+ return stack.fold(0) { acc, elem -> acc * 5 + delimiterValue(elem) }
}
+ }
- return stack.fold(0) { acc, elem -> acc * 5 + delimiterValue(elem) }
- }
- }
- class CorruptedLine(private val invalidDelimiter: ChunkDelimiter) : SyntaxError() {
- val score: Int
- get() = when (invalidDelimiter) {
- ChunkDelimiter.Parentheses -> 3
- ChunkDelimiter.Brackets -> 57
- ChunkDelimiter.Braces -> 1197
- ChunkDelimiter.Angled -> 25137
- }
+ class CorruptedLine(private val invalidDelimiter: ChunkDelimiter) : SyntaxError() {
+ val score: Int
+ get() = when (invalidDelimiter) {
+ ChunkDelimiter.Parentheses -> 3
+ ChunkDelimiter.Brackets -> 57
+ ChunkDelimiter.Braces -> 1197
+ ChunkDelimiter.Angled -> 25137
+ }
+ }
}
-}
-fun parse(line: String): SyntaxError {
- val stack = ArrayDeque<ChunkDelimiter>()
+ private fun parse(line: String): SyntaxError {
+ val stack = ArrayDeque<ChunkDelimiter>()
- for (char in line) {
- when {
- ChunkDelimiter.isOpening(char) -> stack.addFirst(ChunkDelimiter.from(char))
- ChunkDelimiter.isClosing(char) -> {
- val closingDelimiter = ChunkDelimiter.from(char)
- if (stack.first() == closingDelimiter) {
- stack.removeFirst()
- } else {
- return SyntaxError.CorruptedLine(closingDelimiter)
+ for (char in line) {
+ when {
+ ChunkDelimiter.isOpening(char) -> stack.addFirst(ChunkDelimiter.from(char))
+ ChunkDelimiter.isClosing(char) -> {
+ val closingDelimiter = ChunkDelimiter.from(char)
+ if (stack.first() == closingDelimiter) {
+ stack.removeFirst()
+ } else {
+ return SyntaxError.CorruptedLine(closingDelimiter)
+ }
}
}
}
- }
- if (stack.isNotEmpty()) {
- return SyntaxError.IncompleteLine(stack)
- }
+ if (stack.isNotEmpty()) {
+ return SyntaxError.IncompleteLine(stack)
+ }
- return SyntaxError.None
-}
+ return SyntaxError.None
+ }
-fun main() {
fun part1(input: List<String>): Int = input
.map { parse(it) }
.filterIsInstance<SyntaxError.CorruptedLine>()
.sumOf { it.score }
-
+
fun part2(input: List<String>): Long =
input
.asSequence()
@@ -76,13 +78,14 @@ fun main() {
.sorted()
.toList()
.let { it[it.size / 2] }
+}
-
+fun main() {
val testInput = readInputAsLines("Day10_test")
- check(part1(testInput) == 26397)
- check(part2(testInput) == 288957L)
+ check(Day10.part1(testInput) == 26397)
+ check(Day10.part2(testInput) == 288957L)
val input = readInputAsLines("Day10")
- println(part1(input))
- println(part2(input))
+ println(Day10.part1(input))
+ println(Day10.part2(input))
}
diff --git a/src/Day11.kt b/src/Day11.kt
index 9a15344..db56d61 100644
--- a/src/Day11.kt
+++ b/src/Day11.kt
@@ -1,50 +1,48 @@
-fun flashSequence(input: Map<Pos2D, Int>) = sequence {
- val map = input.toMutableMap()
-
- while (true) {
- val flashed = mutableSetOf<Pos2D>()
- fun canFlash(entry: Map.Entry<Pos2D, Int>): Boolean = entry.value > 9 && !flashed.contains(entry.key)
-
- // 1)
- map.forEach { (pos, energy) -> map[pos] = energy + 1 }
-
- // 2)
- while (map.any(::canFlash)) {
- map
- .filter(::canFlash)
- .forEach { (pos, _) ->
- flashed.add(pos)
- Pos2D.directions8.map { pos + it }.forEach {
- if (map.containsKey(it)) {
- map[it] = map[it]!! + 1
+object Day11 {
+ private fun flashSequence(input: Map<Pos2D, Int>) = sequence {
+ val map = input.toMutableMap()
+
+ while (true) {
+ val flashed = mutableSetOf<Pos2D>()
+ fun canFlash(entry: Map.Entry<Pos2D, Int>): Boolean = entry.value > 9 && !flashed.contains(entry.key)
+
+ // 1)
+ map.forEach { (pos, energy) -> map[pos] = energy + 1 }
+
+ // 2)
+ while (map.any(::canFlash)) {
+ map
+ .filter(::canFlash)
+ .forEach { (pos, _) ->
+ flashed.add(pos)
+ Pos2D.directions8.map { pos + it }.forEach {
+ if (map.containsKey(it)) {
+ map[it] = map[it]!! + 1
+ }
}
}
- }
- }
+ }
+
+ // 3)
+ flashed.forEach { map[it] = 0 }
- // 3)
- flashed.forEach { map[it] = 0 }
+ yield(flashed.size)
+ }
+ }
- yield(flashed.size)
+ fun bothParts(input: List<String>) = flashSequence(parseToMap(input)).let { seq ->
+ seq.take(100).sum() to seq.indexOfFirst { it == 100 } + 1
}
}
fun main() {
- fun part1(input: List<String>): Int =
- flashSequence(parseToMap(input))
- .take(100)
- .sum()
-
- fun part2(input: List<String>): Int =
- flashSequence(parseToMap(input))
- .indexOfFirst { it == 100 } + 1
-
-
val testInput = readInputAsLines("Day11_test")
- check(part1(testInput) == 1656)
- check(part2(testInput) == 195)
+ val testOutput = Day11.bothParts(testInput)
+ check(testOutput.first == 1656)
+ check(testOutput.second == 195)
val input = readInputAsLines("Day11")
- println(part1(input))
- println(part2(input))
+ val output = Day11.bothParts(input)
+ println(output.first)
+ println(output.second)
}
diff --git a/src/Day12.kt b/src/Day12.kt
index 57a0390..d3368da 100644
--- a/src/Day12.kt
+++ b/src/Day12.kt
@@ -1,70 +1,72 @@
-class CaveGraph {
- companion object {
- fun fromLines(input: List<String>): CaveGraph =
- CaveGraph().apply { input.forEach { addConnection(it) } }
- }
+object Day12 {
+ private class CaveGraph {
+ companion object {
+ fun fromLines(input: List<String>): CaveGraph =
+ CaveGraph().apply { input.forEach { addConnection(it) } }
+ }
- private val adjacencyList = mutableMapOf<Node, Set<Node>>()
+ private val adjacencyList = mutableMapOf<Node, Set<Node>>()
- fun addConnection(connectionString: String) {
- val (from, to) = connectionString.split("-").map(Node::fromString)
+ private fun addConnection(connectionString: String) {
+ val (from, to) = connectionString.split("-").map(Node::fromString)
- adjacencyList.merge(from, setOf(to), Set<Node>::union)
- adjacencyList.merge(to, setOf(from), Set<Node>::union)
- }
+ adjacencyList.merge(from, setOf(to), Set<Node>::union)
+ adjacencyList.merge(to, setOf(from), Set<Node>::union)
+ }
- fun countPaths(canUseDoubleTraversal: Boolean = false): Int = traverse(Node.Start, setOf(), !canUseDoubleTraversal)
+ fun countPaths(canUseDoubleTraversal: Boolean = false): Int =
+ traverse(Node.Start, setOf(), !canUseDoubleTraversal)
- private fun traverse(from: Node, visitedBefore: Set<Node>, usedUpDoubleTraverse: Boolean): Int {
- return adjacencyList[from]!!.sumOf {
- when (it) {
- is Node.Start -> 0
- is Node.End -> 1
- is Node.Big -> traverse(it, visitedBefore + from, usedUpDoubleTraverse)
- is Node.Small -> {
- if (!visitedBefore.contains(it)) {
- traverse(it, visitedBefore + from, usedUpDoubleTraverse)
- } else if (!usedUpDoubleTraverse) {
- traverse(it, visitedBefore + from, true)
- } else {
- 0
+ private fun traverse(from: Node, visitedBefore: Set<Node>, usedUpDoubleTraverse: Boolean): Int {
+ return adjacencyList[from]!!.sumOf {
+ when (it) {
+ is Node.Start -> 0
+ is Node.End -> 1
+ is Node.Big -> traverse(it, visitedBefore + from, usedUpDoubleTraverse)
+ is Node.Small -> {
+ if (!visitedBefore.contains(it)) {
+ traverse(it, visitedBefore + from, usedUpDoubleTraverse)
+ } else if (!usedUpDoubleTraverse) {
+ traverse(it, visitedBefore + from, true)
+ } else {
+ 0
+ }
}
}
}
}
- }
- sealed class Node {
- companion object {
- fun fromString(text: String): Node = when (text) {
- "start" -> Start
- "end" -> End
- else -> if (text == text.uppercase()) {
- Big(text)
- } else {
- Small(text)
+ private sealed class Node {
+ companion object {
+ fun fromString(text: String): Node = when (text) {
+ "start" -> Start
+ "end" -> End
+ else -> if (text == text.uppercase()) {
+ Big(text)
+ } else {
+ Small(text)
+ }
}
}
- }
- object Start : Node()
- object End : Node()
- data class Small(private val identifier: String) : Node()
- data class Big(private val identifier: String) : Node()
+ object Start : Node()
+ object End : Node()
+ data class Small(private val identifier: String) : Node()
+ data class Big(private val identifier: String) : Node()
+ }
}
+
+ fun bothParts(input: List<String>) = CaveGraph.fromLines(input).let { it.countPaths() to it.countPaths(true) }
}
fun main() {
- fun part1(input: List<String>): Int = CaveGraph.fromLines(input).countPaths()
-
- fun part2(input: List<String>): Int = CaveGraph.fromLines(input).countPaths(true)
-
-
val testInput = readInputAsLines("Day12_test")
- check(part1(testInput) == 226)
- check(part2(testInput) == 3509)
+ val testOutput = Day12.bothParts(testInput)
+ check(testOutput.first == 226)
+ check(testOutput.second == 3509)
val input = readInputAsLines("Day12")
- println(part1(input))
- println(part2(input))
+ val output = Day12.bothParts(input)
+ println(output.first)
+ println(output.second)
}
diff --git a/src/Day13.kt b/src/Day13.kt
index c8bb8c6..0f9b096 100644
--- a/src/Day13.kt
+++ b/src/Day13.kt
@@ -1,64 +1,62 @@
-import java.lang.IllegalStateException
-
-sealed class FoldCommand {
- abstract fun dispatch(dots: Set<Pos2D>): Set<Pos2D>
-
- class AlongX(private val x: Int) : FoldCommand() {
- override fun dispatch(dots: Set<Pos2D>): Set<Pos2D> = dots
- .filter { it.x != x }
- .map {
- if (it.x < x) {
- it
- } else {
- Pos2D(2 * x - it.x, it.y)
+object Day13 {
+ private sealed class FoldCommand {
+ abstract fun dispatch(dots: Set<Pos2D>): Set<Pos2D>
+
+ class AlongX(private val x: Int) : FoldCommand() {
+ override fun dispatch(dots: Set<Pos2D>): Set<Pos2D> = dots
+ .filter { it.x != x }
+ .map {
+ if (it.x < x) {
+ it
+ } else {
+ Pos2D(2 * x - it.x, it.y)
+ }
}
- }
- .toSet()
- }
+ .toSet()
+ }
- class AlongY(private val y: Int) : FoldCommand() {
- override fun dispatch(dots: Set<Pos2D>): Set<Pos2D> = dots
- .filter { it.y != y }
- .map {
- if (it.y < y) {
- it
- } else {
- Pos2D(it.x, 2 * y - it.y)
+ class AlongY(private val y: Int) : FoldCommand() {
+ override fun dispatch(dots: Set<Pos2D>): Set<Pos2D> = dots
+ .filter { it.y != y }
+ .map {
+ if (it.y < y) {
+ it
+ } else {
+ Pos2D(it.x, 2 * y - it.y)
+ }
}
- }
- .toSet()
+ .toSet()
+ }
}
-}
-
-fun parseOrigami(input: List<String>): Pair<Set<Pos2D>, Sequence<FoldCommand>> {
- val dots = mutableSetOf<Pos2D>()
- val commands = mutableListOf<FoldCommand>()
- for (line in input) {
- if (line.matches("\\d+,\\d+".toRegex())) {
- val (x, y) = line.split(",").map(String::toInt)
- dots.add(Pos2D(x, y))
- }
+ private fun parseOrigami(input: List<String>): Pair<Set<Pos2D>, Sequence<FoldCommand>> {
+ val dots = mutableSetOf<Pos2D>()
+ val commands = mutableListOf<FoldCommand>()
- if (line.matches("fold along [xy]=\\d+".toRegex())) {
- val equation = line.substring(11)
- val (axis, valueString) = equation.split("=")
- val value = valueString.toInt()
+ for (line in input) {
+ if (line.matches("\\d+,\\d+".toRegex())) {
+ val (x, y) = line.split(",").map(String::toInt)
+ dots.add(Pos2D(x, y))
+ }
- commands.add(
- when (axis) {
- "x" -> FoldCommand.AlongX(value)
- "y" -> FoldCommand.AlongY(value)
- else -> throw IllegalStateException("Illegal axis given!")
- }
- )
+ if (line.matches("fold along [xy]=\\d+".toRegex())) {
+ val equation = line.substring(11)
+ val (axis, valueString) = equation.split("=")
+ val value = valueString.toInt()
+
+ commands.add(
+ when (axis) {
+ "x" -> FoldCommand.AlongX(value)
+ "y" -> FoldCommand.AlongY(value)
+ else -> throw IllegalStateException("Illegal axis given!")
+ }
+ )
+ }
}
- }
- return dots to commands.asSequence()
-}
+ return dots to commands.asSequence()
+ }
-fun main() {
fun part1(input: List<String>): Int {
val (dots, commands) = parseOrigami(input)
@@ -89,12 +87,13 @@ fun main() {
return lines.joinToString("\n") { it.joinToString("") }
}
+}
-
+fun main() {
val testInput = readInputAsLines("Day13_test")
- check(part1(testInput) == 17)
+ check(Day13.part1(testInput) == 17)
val input = readInputAsLines("Day13")
- println(part1(input))
- println(part2(input))
+ println(Day13.part1(input))
+ println(Day13.part2(input))
}
diff --git a/src/Day14.kt b/src/Day14.kt
index fb6a43e..920ea9e 100644
--- a/src/Day14.kt
+++ b/src/Day14.kt
@@ -1,45 +1,46 @@
-fun getPolymerLetterCounts(input: List<String>, iterations: Int): Long {
- val template = input.first()
- val rules = input.drop(2).associate {
- val (pattern, replacement) = it.split(" -> ")
- (pattern[0] to pattern[1]) to replacement.first()
- }
+object Day14 {
+ private fun getPolymerLetterCounts(input: List<String>, iterations: Int): Long {
+ val template = input.first()
+ val rules = input.drop(2).associate {
+ val (pattern, replacement) = it.split(" -> ")
+ (pattern[0] to pattern[1]) to replacement.first()
+ }
- var pairCounts = template
- .zipWithNext()
- .groupingBy { it }
- .eachCount()
- .mapValues { (_, v) -> v.toLong() }
+ var pairCounts = template
+ .zipWithNext()
+ .groupingBy { it }
+ .eachCount()
+ .mapValues { (_, v) -> v.toLong() }
- repeat(iterations) {
- val newCounts = mutableMapOf<Pair<Char, Char>, Long>()
+ repeat(iterations) {
+ val newCounts = mutableMapOf<Pair<Char, Char>, Long>()
- pairCounts.forEach { (pair, count) ->
- newCounts.merge(rules[pair]!! to pair.second, count, Long::plus)
- newCounts.merge(pair.first to rules[pair]!!, count, Long::plus)
- }
+ pairCounts.forEach { (pair, count) ->
+ newCounts.merge(rules[pair]!! to pair.second, count, Long::plus)
+ newCounts.merge(pair.first to rules[pair]!!, count, Long::plus)
+ }
- pairCounts = newCounts
- }
+ pairCounts = newCounts
+ }
- val letterCounts = mutableMapOf<Char, Long>()
- pairCounts.forEach { (pair, count) -> letterCounts.merge(pair.second, count, Long::plus) }
- letterCounts.merge(template.first(), 1, Long::plus)
+ val letterCounts = mutableMapOf<Char, Long>()
+ pairCounts.forEach { (pair, count) -> letterCounts.merge(pair.second, count, Long::plus) }
+ letterCounts.merge(template.first(), 1, Long::plus)
- return letterCounts.values.let { it.maxOrNull()!! - it.minOrNull()!! }
-}
+ return letterCounts.values.let { it.maxOrNull()!! - it.minOrNull()!! }
+ }
-fun main() {
fun part1(input: List<String>): Long = getPolymerLetterCounts(input, 10)
fun part2(input: List<String>): Long = getPolymerLetterCounts(input, 40)
+}
-
+fun main() {
val testInput = readInputAsLines("Day14_test")
- check(part1(testInput) == 1588L)
- check(part2(testInput) == 2188189693529L)
+ check(Day14.part1(testInput) == 1588L)
+ check(Day14.part2(testInput) == 2188189693529L)
val input = readInputAsLines("Day14")
- println(part1(input))
- println(part2(input))
+ println(Day14.part1(input))
+ println(Day14.part2(input))
}
diff --git a/src/Day15.kt b/src/Day15.kt
index abb4bfc..6d0de5c 100644
--- a/src/Day15.kt
+++ b/src/Day15.kt
@@ -1,128 +1,117 @@
import java.util.*
-class CustomPriorityQueue<T>(private val array: Array<T>, private val comparator: Comparator<T>) {
- init {
- array.sortWith(comparator)
- }
-
- private var i = 0
-
- private fun indexOfStartingFrom(elem: T, start: Int): Int {
- for (i in (start until array.size)) {
- if (array[i] == elem) {
- return i
- }
+object Day15 {
+ private class CustomPriorityQueue<T>(private val array: Array<T>, private val comparator: Comparator<T>) {
+ init {
+ array.sortWith(comparator)
}
- return -1
- }
-
- fun isNotEmpty(): Boolean = i < array.size
+ private var i = 0
- fun take(): T = array[i++]
-
- fun revalidate(elem: T) {
- val currentIndex = indexOfStartingFrom(elem, i)
+ private fun indexOfStartingFrom(elem: T, start: Int): Int {
+ for (i in (start until array.size)) {
+ if (array[i] == elem) {
+ return i
+ }
+ }
- if (currentIndex == -1) {
- return
+ return -1
}
- val newIndex = Arrays
- .binarySearch(array, i, currentIndex, elem, comparator)
- .let { if (it >= 0) it else -(it + 1) }
+ fun isNotEmpty(): Boolean = i < array.size
- System.arraycopy(array, newIndex, array, newIndex + 1, currentIndex - newIndex)
+ fun take(): T = array[i++]
- array[newIndex] = elem
- }
-}
+ fun revalidate(elem: T) {
+ val currentIndex = indexOfStartingFrom(elem, i)
-class Matrix(private val data: Array<IntArray>, private val size: Int) {
- companion object {
- fun fromInput(input: List<String>): Matrix {
- val data = input.map { line ->
- line.map { it.digitToInt() }.toIntArray()
- }.toTypedArray()
+ if (currentIndex == -1) {
+ return
+ }
- check(data.isNotEmpty()) { "No rows found!" }
- check(data.first().isNotEmpty()) { "No columns found!" }
+ val newIndex = Arrays
+ .binarySearch(array, i, currentIndex, elem, comparator)
+ .let { if (it >= 0) it else -(it + 1) }
- return Matrix(data, data.size)
+ System.arraycopy(array, newIndex, array, newIndex + 1, currentIndex - newIndex)
+
+ array[newIndex] = elem
}
}
- private fun inMatrix(row: Int, col: Int): Boolean = row in 0 until size && col in 0 until size
-
- fun set(row: Int, col: Int, value: Int) {
- check(inMatrix(row, col)) { "Invalid position!" }
- data[row][col] = value
- }
+ private class Matrix(private val data: Array<IntArray>, private val size: Int) {
+ companion object {
+ fun fromInput(input: List<String>): Matrix {
+ val data = input.map { line ->
+ line.map { it.digitToInt() }.toIntArray()
+ }.toTypedArray()
- fun get(row: Int, col: Int): Int {
- check(inMatrix(row, col)) { "Invalid position!" }
- return data[row][col]
- }
+ check(data.isNotEmpty()) { "No rows found!" }
+ check(data.first().isNotEmpty()) { "No columns found!" }
- fun expand(): Matrix {
- val expandedData = Array(size * 5) { row ->
- IntArray(size * 5) { col ->
- ((data[row % size][col % size] + row / size + col / size) - 1) % 9 + 1
+ return Matrix(data, data.size)
}
}
- return Matrix(expandedData, size * 5)
- }
+ fun expand(): Matrix {
+ val expandedData = Array(size * 5) { row ->
+ IntArray(size * 5) { col ->
+ ((data[row % size][col % size] + row / size + col / size) - 1) % 9 + 1
+ }
+ }
- private fun neighbours(pos: Pair<Int, Int>): Sequence<Pair<Int, Int>> {
- val offsets = sequenceOf(0 to 1, 1 to 0, 0 to -1, -1 to 0)
+ return Matrix(expandedData, size * 5)
+ }
- return offsets
- .map { pos.first + it.first to pos.second + it.second }
- .filter { it.first in 0 until size && it.second in 0 until size }
- }
+ private fun neighbours(pos: Pair<Int, Int>): Sequence<Pair<Int, Int>> {
+ val offsets = sequenceOf(0 to 1, 1 to 0, 0 to -1, -1 to 0)
+
+ return offsets
+ .map { pos.first + it.first to pos.second + it.second }
+ .filter { it.first in 0 until size && it.second in 0 until size }
+ }
- fun dijkstra(): Int {
- val positions = (0 until size)
- .flatMap { row -> (0 until size).map { col -> row to col } }
+ fun dijkstra(): Int {
+ val positions = (0 until size)
+ .flatMap { row -> (0 until size).map { col -> row to col } }
- val distances =
- positions
- .associateWith { Int.MAX_VALUE }
- .toMutableMap()
+ val distances =
+ positions
+ .associateWith { Int.MAX_VALUE }
+ .toMutableMap()
- distances[0 to 0] = 0
+ distances[0 to 0] = 0
- val queue = CustomPriorityQueue(positions.toTypedArray(), compareBy { distances[it]!! })
+ val queue = CustomPriorityQueue(positions.toTypedArray(), compareBy { distances[it]!! })
- while (queue.isNotEmpty()) {
- val u = queue.take()
+ while (queue.isNotEmpty()) {
+ val u = queue.take()
- for (v in neighbours(u)) {
- val newDist = distances[u]!! + data[v.first][v.second]
- if (distances[v]!! > newDist) {
- distances[v] = newDist
- queue.revalidate(v)
+ for (v in neighbours(u)) {
+ val newDist = distances[u]!! + data[v.first][v.second]
+ if (distances[v]!! > newDist) {
+ distances[v] = newDist
+ queue.revalidate(v)
+ }
}
}
- }
- return distances[size - 1 to size - 1]!!
+ return distances[size - 1 to size - 1]!!
+ }
}
-}
-fun main() {
fun part1(input: List<String>): Int = Matrix.fromInput(input).dijkstra()
fun part2(input: List<String>): Int = Matrix.fromInput(input).expand().dijkstra()
+}
-
+fun main() {
val testInput = readInputAsLines("Day15_test")
- check(part1(testInput) == 40)
- check(part2(testInput) == 315)
+ check(Day15.part1(testInput) == 40)
+ check(Day15.part2(testInput) == 315)
val input = readInputAsLines("Day15")
- println(part1(input))
- println(part2(input))
+ println(Day15.part1(input))
+ println(Day15.part2(input))
}
diff --git a/src/Day16.kt b/src/Day16.kt
index f8b287c..d11219d 100644
--- a/src/Day16.kt
+++ b/src/Day16.kt
@@ -1,120 +1,123 @@
-import kotlin.collections.ArrayDeque
-
-sealed class Packet(
- private val bits: Int,
- protected val version: Int,
-) {
- class Literal(
- bits: Int,
- version: Int,
- private val literalValue: Long,
- ) : Packet(bits, version) {
- override fun versionSum(): Int = version
-
- override val value: Long
- get() = literalValue
- }
+object Day16 {
+ private sealed class Packet(
+ private val bits: Int,
+ protected val version: Int,
+ ) {
+ private class Literal(
+ bits: Int,
+ version: Int,
+ private val literalValue: Long,
+ ) : Packet(bits, version) {
+ override fun versionSum(): Int = version
+
+ override val value: Long
+ get() = literalValue
+ }
- class Operator(
- bits: Int,
- version: Int,
- private val typeId: Int,
- private val subpackets: List<Packet>,
- ) : Packet(bits, version) {
- override fun versionSum(): Int = version + subpackets.sumOf { it.versionSum() }
-
- override val value: Long
- get() = when (typeId) {
- 0 -> subpackets.sumOf { it.value }
- 1 -> subpackets.fold(1) { acc, packet -> acc * packet.value }
- 2 -> subpackets.minOf { it.value }
- 3 -> subpackets.maxOf { it.value }
- 5, 6, 7 -> {
- val (first, second) = subpackets.map { it.value }
-
- if (when (typeId) {
- 5 -> first > second
- 6 -> first < second
- 7 -> first == second
- else -> false
- }) {
- 1
- } else {
- 0
+ private class Operator(
+ bits: Int,
+ version: Int,
+ private val typeId: Int,
+ private val subpackets: List<Packet>,
+ ) : Packet(bits, version) {
+ override fun versionSum(): Int = version + subpackets.sumOf { it.versionSum() }
+
+ override val value: Long
+ get() = when (typeId) {
+ 0 -> subpackets.sumOf { it.value }
+ 1 -> subpackets.fold(1) { acc, packet -> acc * packet.value }
+ 2 -> subpackets.minOf { it.value }
+ 3 -> subpackets.maxOf { it.value }
+ 5, 6, 7 -> {
+ val (first, second) = subpackets.map { it.value }
+
+ if (when (typeId) {
+ 5 -> first > second
+ 6 -> first < second
+ 7 -> first == second
+ else -> false
+ }
+ ) {
+ 1
+ } else {
+ 0
+ }
}
+
+ else -> throw IllegalStateException("Illegal packet type id.")
}
- else -> throw IllegalStateException("Illegal packet type id.")
- }
- }
+ }
- abstract fun versionSum(): Int
+ abstract fun versionSum(): Int
- abstract val value: Long
+ abstract val value: Long
- companion object {
- fun parse(bitQueue: ArrayDeque<Char>): Packet {
- var packetBits = 0
+ companion object {
+ fun parse(bitQueue: ArrayDeque<Char>): Packet {
+ var packetBits = 0
- fun takeBits(n: Int): Int {
- packetBits += n
- return (0 until n)
- .joinToString("") { bitQueue.removeFirst().toString() }
- .toInt(2)
- }
+ fun takeBits(n: Int): Int {
+ packetBits += n
+ return (0 until n)
+ .joinToString("") { bitQueue.removeFirst().toString() }
+ .toInt(2)
+ }
- val version = takeBits(3)
+ val version = takeBits(3)
- when (val typeId = takeBits(3)) {
- 4 -> { // Literal packet
- var literalValue = 0L
- while (true) {
- val groupHeader = takeBits(1)
- val groupValue = takeBits(4)
+ when (val typeId = takeBits(3)) {
+ 4 -> { // Literal packet
+ var literalValue = 0L
+ while (true) {
+ val groupHeader = takeBits(1)
+ val groupValue = takeBits(4)
- literalValue = (literalValue shl 4) + groupValue
+ literalValue = (literalValue shl 4) + groupValue
- if (groupHeader == 0) {
- break
+ if (groupHeader == 0) {
+ break
+ }
}
+
+ return Literal(packetBits, version, literalValue)
}
- return Literal(packetBits, version, literalValue)
- }
- else -> { // Operator packet
- val subpackets = mutableListOf<Packet>()
-
- when (takeBits(1)) {
- 0 -> {
- val subpacketLength = takeBits(15)
-
- var currentSubpacketLength = 0
- while (currentSubpacketLength < subpacketLength) {
- val subpacket = parse(bitQueue)
- currentSubpacketLength += subpacket.bits
- subpackets.add(subpacket)
+ else -> { // Operator packet
+ val subpackets = mutableListOf<Packet>()
+
+ when (takeBits(1)) {
+ 0 -> {
+ val subpacketLength = takeBits(15)
+
+ var currentSubpacketLength = 0
+ while (currentSubpacketLength < subpacketLength) {
+ val subpacket = parse(bitQueue)
+ currentSubpacketLength += subpacket.bits
+ subpackets.add(subpacket)
+ }
}
- }
- 1 -> {
- val subpacketCount = takeBits(11)
- repeat(subpacketCount) {
- subpackets.add(parse(bitQueue))
+ 1 -> {
+ val subpacketCount = takeBits(11)
+
+ repeat(subpacketCount) {
+ subpackets.add(parse(bitQueue))
+ }
}
+
+ else -> throw IllegalStateException("Illegal length type id.")
}
- else -> throw IllegalStateException("Illegal length type id.")
- }
- packetBits += subpackets.sumOf { it.bits }
+ packetBits += subpackets.sumOf { it.bits }
- return Operator(packetBits, version, typeId, subpackets)
+ return Operator(packetBits, version, typeId, subpackets)
+ }
}
}
}
}
-}
-fun main() {
- fun parse(input: String): Packet {
+ private fun parse(input: String): Packet {
val bitQueue = ArrayDeque(
input
.flatMap {
@@ -133,23 +136,26 @@ fun main() {
fun part1(input: String): Int = parse(input).versionSum()
fun part2(input: String): Long = parse(input).value
+}
+
- check(part1("8A004A801A8002F478") == 16)
- check(part1("620080001611562C8802118E34") == 12)
- check(part1("C0015000016115A2E0802F182340") == 23)
- check(part1("A0016C880162017C3686B18A3D4780") == 31)
+fun main() {
+ check(Day16.part1("8A004A801A8002F478") == 16)
+ check(Day16.part1("620080001611562C8802118E34") == 12)
+ check(Day16.part1("C0015000016115A2E0802F182340") == 23)
+ check(Day16.part1("A0016C880162017C3686B18A3D4780") == 31)
- check(part2("C200B40A82") == 3L)
- check(part2("04005AC33890") == 54L)
- check(part2("880086C3E88112") == 7L)
- check(part2("CE00C43D881120") == 9L)
- check(part2("D8005AC2A8F0") == 1L)
- check(part2("F600BC2D8F") == 0L)
- check(part2("9C005AC2F8F0") == 0L)
- check(part2("9C0141080250320F1802104A08") == 1L)
+ check(Day16.part2("C200B40A82") == 3L)
+ check(Day16.part2("04005AC33890") == 54L)
+ check(Day16.part2("880086C3E88112") == 7L)
+ check(Day16.part2("CE00C43D881120") == 9L)
+ check(Day16.part2("D8005AC2A8F0") == 1L)
+ check(Day16.part2("F600BC2D8F") == 0L)
+ check(Day16.part2("9C005AC2F8F0") == 0L)
+ check(Day16.part2("9C0141080250320F1802104A08") == 1L)
val input = readInputAsString("Day16")
- println(part1(input))
- println(part2(input))
+ println(Day16.part1(input))
+ println(Day16.part2(input))
}
diff --git a/src/Day17.kt b/src/Day17.kt
index 5eaf498..5e0170b 100644
--- a/src/Day17.kt
+++ b/src/Day17.kt
@@ -1,60 +1,62 @@
import kotlin.math.max
import kotlin.math.sign
-data class Target(val left: Int, val right: Int, val bottom: Int, val top: Int)
-
-class Probe(private var vel: Pos2D, private val target: Target) {
- companion object {
- fun search(target: Target): Pair<Int, Int> {
- var highest = 0
- var count = 0
-
- for (vx in 0..1000) {
- for (vy in -1000..1000) {
- val probe = Probe(Pos2D(vx, vy), target)
- var currentHighest = 0
-
- while (probe.canHitTarget) {
- probe.step()
- currentHighest = max(currentHighest, probe.pos.y)
-
- if (probe.isInTarget) {
- count++
- highest = max(highest, currentHighest)
- break
+object Day17 {
+ data class Target(val left: Int, val right: Int, val bottom: Int, val top: Int)
+
+ private class Probe(private var vel: Pos2D, private val target: Target) {
+ companion object {
+ fun search(target: Target): Pair<Int, Int> {
+ var highest = 0
+ var count = 0
+
+ for (vx in 0..1000) {
+ for (vy in -1000..1000) {
+ val probe = Probe(Pos2D(vx, vy), target)
+ var currentHighest = 0
+
+ while (probe.canHitTarget) {
+ probe.step()
+ currentHighest = max(currentHighest, probe.pos.y)
+
+ if (probe.isInTarget) {
+ count++
+ highest = max(highest, currentHighest)
+ break
+ }
}
}
}
+
+ return highest to count
}
+ }
+
+ private var pos = Pos2D(0, 0)
- return highest to count
+ private fun step() {
+ pos += vel
+ vel = vel.copy(x = vel.x - vel.x.sign, y = vel.y - 1)
}
- }
- private var pos = Pos2D(0, 0)
+ private val canHitTarget
+ get() = pos.y > target.bottom
- fun step() {
- pos += vel
- vel = vel.copy(x = vel.x - vel.x.sign, y = vel.y - 1)
+ private val isInTarget
+ get() = pos.x in target.left..target.right && pos.y in target.bottom..target.top
}
- val canHitTarget
- get() = pos.y > target.bottom
-
- val isInTarget
- get() = pos.x in target.left..target.right && pos.y in target.bottom..target.top
+ fun bothParts(input: Target) = Probe.search(input)
}
fun main() {
- fun bothParts(input: Target): Pair<Int, Int> = Probe.search(input)
-
- val testInput = Target(20, 30, -10, -5)
- val testOutput = bothParts(testInput)
+ val testInput = Day17.Target(20, 30, -10, -5)
+ val testOutput = Day17.bothParts(testInput)
check(testOutput.first == 45)
check(testOutput.second == 112)
- val input = Target(192, 251, -89, -59)
- val output = bothParts(input)
+ val input = Day17.Target(192, 251, -89, -59)
+ val output = Day17.bothParts(input)
println(output.first)
println(output.second)
}
diff --git a/src/Day18.kt b/src/Day18.kt
index f69e916..84575b7 100644
--- a/src/Day18.kt
+++ b/src/Day18.kt
@@ -1,114 +1,114 @@
import kotlin.math.ceil
import kotlin.math.floor
-class SnailfishNum private constructor(private val data: MutableList<Entry>) {
- data class Entry(var num: Int, val depth: Int)
-
- companion object {
- fun parse(input: String): SnailfishNum {
- var depth = 0
- val list = mutableListOf<Entry>()
-
- for (char in input) {
- when (char) {
- '[' -> depth += 1
- ']' -> depth -= 1
- ',' -> {}
- else -> list.add(Entry(char.toString().toInt(), depth))
+object Day18 {
+ private class SnailfishNum private constructor(private val data: MutableList<Entry>) {
+ private data class Entry(var num: Int, val depth: Int)
+
+ companion object {
+ fun parse(input: String): SnailfishNum {
+ var depth = 0
+ val list = mutableListOf<Entry>()
+
+ for (char in input) {
+ when (char) {
+ '[' -> depth += 1
+ ']' -> depth -= 1
+ ',' -> {}
+ else -> list.add(Entry(char.toString().toInt(), depth))
+ }
}
- }
- return SnailfishNum(list)
+ return SnailfishNum(list)
+ }
}
- }
- private val shouldExplode
- get() = data
- .zipWithNext()
- .any { it.first.depth == it.second.depth && it.first.depth >= 5 }
+ private val shouldExplode
+ get() = data
+ .zipWithNext()
+ .any { it.first.depth == it.second.depth && it.first.depth >= 5 }
- private val shouldSplit
- get() = data.any { it.num >= 10 }
+ private val shouldSplit
+ get() = data.any { it.num >= 10 }
- private fun explode() {
- val (leftIndex, rightIndex) = data
- .zipWithNext()
- .indexOfFirst { it.first.depth == it.second.depth && it.first.depth >= 5 }
- .let { it to it + 1 }
+ private fun explode() {
+ val (leftIndex, rightIndex) = data
+ .zipWithNext()
+ .indexOfFirst { it.first.depth == it.second.depth && it.first.depth >= 5 }
+ .let { it to it + 1 }
- if (leftIndex - 1 in data.indices) {
- data[leftIndex - 1].num += data[leftIndex].num
- }
+ if (leftIndex - 1 in data.indices) {
+ data[leftIndex - 1].num += data[leftIndex].num
+ }
- if (rightIndex + 1 in data.indices) {
- data[rightIndex + 1].num += data[rightIndex].num
- }
+ if (rightIndex + 1 in data.indices) {
+ data[rightIndex + 1].num += data[rightIndex].num
+ }
- data[leftIndex] = Entry(0, data[leftIndex].depth - 1)
- data.removeAt(rightIndex)
- }
+ data[leftIndex] = Entry(0, data[leftIndex].depth - 1)
+ data.removeAt(rightIndex)
+ }
- private fun split() {
- val index = data.indexOfFirst { it.num >= 10 }
- val depth = data[index].depth
+ private fun split() {
+ val index = data.indexOfFirst { it.num >= 10 }
+ val depth = data[index].depth
- val half = data[index].num / 2.0
- val roundedDown = floor(half).toInt()
- val roundedUp = ceil(half).toInt()
+ val half = data[index].num / 2.0
+ val roundedDown = floor(half).toInt()
+ val roundedUp = ceil(half).toInt()
- data[index] = Entry(roundedUp, depth + 1)
- data.add(index, Entry(roundedDown, depth + 1))
- }
+ data[index] = Entry(roundedUp, depth + 1)
+ data.add(index, Entry(roundedDown, depth + 1))
+ }
- private fun reduce() {
- while (true) {
- if (shouldExplode) {
- explode()
- } else if (shouldSplit) {
- split()
- } else {
- break
+ private fun reduce() {
+ while (true) {
+ if (shouldExplode) {
+ explode()
+ } else if (shouldSplit) {
+ split()
+ } else {
+ break
+ }
}
}
- }
- fun magnitude(): Int {
- val dataCopy = data.toMutableList()
+ fun magnitude(): Int {
+ val dataCopy = data.toMutableList()
- while (dataCopy.size > 1) {
- val maxDepth = dataCopy.maxOf { it.depth }
+ while (dataCopy.size > 1) {
+ val maxDepth = dataCopy.maxOf { it.depth }
- val (leftIndex, rightIndex) = dataCopy
- .zipWithNext()
- .indexOfFirst { it.first.depth == it.second.depth && it.first.depth == maxDepth }
- .let { it to it + 1 }
+ val (leftIndex, rightIndex) = dataCopy
+ .zipWithNext()
+ .indexOfFirst { it.first.depth == it.second.depth && it.first.depth == maxDepth }
+ .let { it to it + 1 }
- dataCopy[leftIndex] = Entry(
- dataCopy[leftIndex].num * 3 + dataCopy[rightIndex].num * 2,
- maxDepth - 1
- )
- dataCopy.removeAt(rightIndex)
+ dataCopy[leftIndex] = Entry(
+ dataCopy[leftIndex].num * 3 + dataCopy[rightIndex].num * 2,
+ maxDepth - 1
+ )
+ dataCopy.removeAt(rightIndex)
+ }
+
+ return dataCopy.first().num
}
- return dataCopy.first().num
+ operator fun plus(other: SnailfishNum): SnailfishNum =
+ SnailfishNum(
+ (this.data + other.data).map { Entry(it.num, it.depth + 1) }.toMutableList()
+ ).also { it.reduce() }
}
- operator fun plus(other: SnailfishNum): SnailfishNum =
- SnailfishNum(
- (this.data + other.data).map { Entry(it.num, it.depth + 1) }.toMutableList()
- ).also { it.reduce() }
-}
-
-fun <T> combinations(items: Sequence<T>): Sequence<Pair<T, T>> =
- sequence {
- items.forEach { a ->
- items.forEach { b ->
- yield(a to b)
+ private fun <T> combinations(items: Sequence<T>): Sequence<Pair<T, T>> =
+ sequence {
+ items.forEach { a ->
+ items.forEach { b ->
+ yield(a to b)
+ }
}
}
- }
-fun main() {
fun part1(input: List<String>): Int =
input
.asSequence()
@@ -124,13 +124,14 @@ fun main() {
)
.filter { it.first !== it.second }
.maxOf { (it.first + it.second).magnitude() }
+}
-
+fun main() {
val testInput = readInputAsLines("Day18_test")
- check(part1(testInput) == 4140)
- check(part2(testInput) == 3993)
+ check(Day18.part1(testInput) == 4140)
+ check(Day18.part2(testInput) == 3993)
val input = readInputAsLines("Day18")
- println(part1(input))
- println(part2(input))
+ println(Day18.part1(input))
+ println(Day18.part2(input))
}
diff --git a/src/Day24.kt b/src/Day24.kt
index 41ea440..9ca300c 100644
--- a/src/Day24.kt
+++ b/src/Day24.kt
@@ -121,16 +121,16 @@ object Day24 {
val currentStep = mutableListOf<Instruction.Binary>()
var currentInput: Instruction.Input? = null
- for (instruction in instructionQueue) {
- when (instruction) {
+ instructionQueue.forEach {
+ when (it) {
is Instruction.Input -> {
parts.add(Part(currentInput, currentStep.toList()))
currentStep.clear()
- currentInput = instruction
+ currentInput = it
}
- is Instruction.Binary -> currentStep.add(instruction)
+ is Instruction.Binary -> currentStep.add(it)
}
}
@@ -142,10 +142,7 @@ object Day24 {
private data class Part(val input: Instruction.Input?, val instructionBatch: List<Instruction.Binary>)
- private data class Checkpoint(
- val partNumber: Int,
- val alu: ALU,
- )
+ private data class Checkpoint(val partNumber: Int, val alu: ALU)
fun findInputDigits(
digitRange: IntProgression = 0..9,
@@ -168,14 +165,14 @@ object Day24 {
return@sequence
}
- for (digit in digitRange) {
+ digitRange.forEach {
yieldAll(
solveRecursively(
Checkpoint(
checkpoint.partNumber + 1,
- executePart(checkpoint.partNumber, checkpoint.alu, digit.toLong()),
+ executePart(checkpoint.partNumber, checkpoint.alu, it.toLong()),
),
- accumulator * 10 + digit,
+ accumulator * 10 + it,
)
)
}
@@ -197,10 +194,10 @@ object Day24 {
var checkpoint = Checkpoint(1, executePart(0))
yield(checkpoint)
- for (digit in input.toString().toCharArray().map { it.toString().toInt() }) {
+ input.toString().toCharArray().map { it.toString().toInt() }.forEach {
checkpoint = Checkpoint(
checkpoint.partNumber + 1,
- executePart(checkpoint.partNumber, checkpoint.alu, digit.toLong())
+ executePart(checkpoint.partNumber, checkpoint.alu, it.toLong())
)
yield(checkpoint)
}
diff --git a/src/Day25.kt b/src/Day25.kt
index 309c15e..36a2f75 100644
--- a/src/Day25.kt
+++ b/src/Day25.kt
@@ -1,85 +1,87 @@
-class SeaCucumbers(private val width: Int, private val height: Int, private val matrix: Array<SeaTile>) {
- companion object {
- fun fromLines(lines: List<String>) =
- Pos2D(
- lines.getOrNull(0)?.length ?: throw IllegalArgumentException("Sea must have a non-zero height!"),
- lines.size
- ).let { size ->
- SeaCucumbers(size.x, size.y, Array(size.x * size.y) {
- when (val char = lines[it / size.x][it % size.x]) {
- '>' -> SeaTile.Cucumber.EastFacing
- 'v' -> SeaTile.Cucumber.SouthFacing
- '.' -> SeaTile.EmptyTile
- else -> throw IllegalArgumentException("Found '$char', expected '>', 'v' or '.'!")
- }
- })
- }
- }
+object Day25 {
+ private class SeaCucumbers(private val width: Int, private val height: Int, private val matrix: Array<SeaTile>) {
+ companion object {
+ fun fromLines(lines: List<String>) =
+ Pos2D(
+ lines.getOrNull(0)?.length ?: throw IllegalArgumentException("Sea must have a non-zero height!"),
+ lines.size
+ ).let { size ->
+ SeaCucumbers(size.x, size.y, Array(size.x * size.y) {
+ when (val char = lines[it / size.x][it % size.x]) {
+ '>' -> SeaTile.Cucumber.EastFacing
+ 'v' -> SeaTile.Cucumber.SouthFacing
+ '.' -> SeaTile.EmptyTile
+ else -> throw IllegalArgumentException("Found '$char', expected '>', 'v' or '.'!")
+ }
+ })
+ }
+ }
- sealed class SeaTile {
- object EmptyTile : SeaTile()
- sealed class Cucumber : SeaTile() {
- object EastFacing : Cucumber()
- object SouthFacing : Cucumber()
+ private sealed class SeaTile {
+ object EmptyTile : SeaTile()
+ sealed class Cucumber : SeaTile() {
+ object EastFacing : Cucumber()
+ object SouthFacing : Cucumber()
+ }
}
- }
- private fun moveIndex(index: Int, offset: Pos2D) =
- ((index / width + offset.y) % height) * width + (index + offset.x) % width
+ private fun moveIndex(index: Int, offset: Pos2D) =
+ ((index / width + offset.y) % height) * width + (index + offset.x) % width
- private inline fun <reified T : SeaTile.Cucumber> stepDirection(pos: Pos2D) {
- matrix
- .asSequence()
- .withIndex()
- .map { (index, seaTile) ->
- val nextIndex = moveIndex(index, pos)
- if (seaTile is T && matrix[nextIndex] is SeaTile.EmptyTile) {
- index to nextIndex
- } else {
- null
+ private inline fun <reified T : SeaTile.Cucumber> stepDirection(pos: Pos2D) {
+ matrix
+ .asSequence()
+ .withIndex()
+ .map { (index, seaTile) ->
+ val nextIndex = moveIndex(index, pos)
+ if (seaTile is T && matrix[nextIndex] is SeaTile.EmptyTile) {
+ index to nextIndex
+ } else {
+ null
+ }
}
- }
- .filterNotNull()
- .toList()
- .forEach { (index, nextIndex) ->
- matrix[nextIndex] = matrix[index].also { matrix[index] = SeaTile.EmptyTile }
- }
- }
+ .filterNotNull()
+ .toList()
+ .forEach { (index, nextIndex) ->
+ matrix[nextIndex] = matrix[index].also { matrix[index] = SeaTile.EmptyTile }
+ }
+ }
- private fun stepOnce() {
- stepDirection<SeaTile.Cucumber.EastFacing>(Pos2D(1, 0))
- stepDirection<SeaTile.Cucumber.SouthFacing>(Pos2D(0, 1))
- }
+ private fun stepOnce() {
+ stepDirection<SeaTile.Cucumber.EastFacing>(Pos2D(1, 0))
+ stepDirection<SeaTile.Cucumber.SouthFacing>(Pos2D(0, 1))
+ }
- fun simulate(): Int {
- var count = 0
+ fun simulate(): Int {
+ var count = 0
- do {
- val previousHashCode = matrix.contentHashCode()
- stepOnce()
- count++
- } while (matrix.contentHashCode() != previousHashCode)
+ do {
+ val previousHashCode = matrix.contentHashCode()
+ stepOnce()
+ count++
+ } while (matrix.contentHashCode() != previousHashCode)
- return count
+ return count
+ }
+
+ override fun toString(): String = matrix
+ .withIndex()
+ .joinToString("") { (index, seaTile) ->
+ when (seaTile) {
+ SeaTile.Cucumber.EastFacing -> ">"
+ SeaTile.Cucumber.SouthFacing -> "v"
+ SeaTile.EmptyTile -> "."
+ } + (if (index % width == width - 1) "\n" else "")
+ }
}
- override fun toString(): String = matrix
- .withIndex()
- .joinToString("") { (index, seaTile) ->
- when (seaTile) {
- SeaTile.Cucumber.EastFacing -> ">"
- SeaTile.Cucumber.SouthFacing -> "v"
- SeaTile.EmptyTile -> "."
- } + (if (index % width == width - 1) "\n" else "")
- }
+ fun singlePart(input: List<String>) = SeaCucumbers.fromLines(input).simulate()
}
fun main() {
- fun calculate(input: List<String>) = SeaCucumbers.fromLines(input).simulate()
-
val testInput = readInputAsLines("Day25_test")
- check(calculate(testInput) == 58)
+ check(Day25.singlePart(testInput) == 58)
val input = readInputAsLines("Day25")
- println(calculate(input))
+ println(Day25.singlePart(input))
}
diff --git a/src/Utils.kt b/src/Utils.kt
index e245554..f0a420b 100644
--- a/src/Utils.kt
+++ b/src/Utils.kt
@@ -1,31 +1,11 @@
import java.io.File
-/**
- * Reads lines from the given input txt file.
- * @param name name of the file
- * @return list of strings containing line contents
- */
fun readInputAsLines(name: String): List<String> = File("src", "$name.txt").readLines()
-/**
- * Returns a string of contents of the given input txt file.
- * @param name name of the file
- * @return contents of file as string
- */
fun readInputAsString(name: String): String = File("src", "$name.txt").readText()
-/**
- * Read lines from the given input txt file and convert them to decimal numbers.
- * @param name name of the file
- * @return list of ints containing numbers from each of file's lines
- */
fun readInputAsNumbers(name: String): List<Int> = readInputAsLines(name).map(String::toInt)
-/**
- * Read lines from the given input txt file containing binary numbers and convert them to lists of bits.
- * @param name name of the file
- * @return list of lists of ints, where each inner list represents bits of one line of input
- */
fun readInputAsBitLists(name: String): List<List<Int>> =
readInputAsLines(name)
.map { binaryString -> binaryString.toList().map { bit -> bit.toString().toInt() } }