diff options
author | tchojnacki <tomaszchojnacki2001@gmail.com> | 2022-08-10 21:01:47 +0200 |
---|---|---|
committer | tchojnacki <tomaszchojnacki2001@gmail.com> | 2022-08-10 21:01:47 +0200 |
commit | 5ef64073bbb183dd425ffa360d080ca58a1c08e1 (patch) | |
tree | 9a108a0ed6d0ee7bb6799dc2260cebef2fb96723 | |
parent | 5a4e32c427862238fd092cbc28be4622d1552a72 (diff) | |
download | gleam_aoc2020-5ef64073bbb183dd425ffa360d080ca58a1c08e1.tar.gz gleam_aoc2020-5ef64073bbb183dd425ffa360d080ca58a1c08e1.zip |
Refactor all days to provide better encapsulation
-rw-r--r-- | src/Day01.kt | 16 | ||||
-rw-r--r-- | src/Day02.kt | 34 | ||||
-rw-r--r-- | src/Day03.kt | 81 | ||||
-rw-r--r-- | src/Day04.kt | 124 | ||||
-rw-r--r-- | src/Day05.kt | 61 | ||||
-rw-r--r-- | src/Day06.kt | 50 | ||||
-rw-r--r-- | src/Day07.kt | 12 | ||||
-rw-r--r-- | src/Day08.kt | 13 | ||||
-rw-r--r-- | src/Day09.kt | 19 | ||||
-rw-r--r-- | src/Day10.kt | 115 | ||||
-rw-r--r-- | src/Day11.kt | 74 | ||||
-rw-r--r-- | src/Day12.kt | 100 | ||||
-rw-r--r-- | src/Day13.kt | 109 | ||||
-rw-r--r-- | src/Day14.kt | 61 | ||||
-rw-r--r-- | src/Day15.kt | 159 | ||||
-rw-r--r-- | src/Day16.kt | 218 | ||||
-rw-r--r-- | src/Day17.kt | 78 | ||||
-rw-r--r-- | src/Day18.kt | 175 | ||||
-rw-r--r-- | src/Day24.kt | 23 | ||||
-rw-r--r-- | src/Day25.kt | 136 | ||||
-rw-r--r-- | src/Utils.kt | 20 |
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() } } |