From 827d6bf2674286942eb092bb1f0a026704047150 Mon Sep 17 00:00:00 2001 From: Ivan Reshetnikov Date: Fri, 20 Dec 2024 00:03:02 +0500 Subject: [PATCH] Refactor day 3 of AoC 2023 --- advent-of-code/2023/day_03/.gitignore | 43 +++++ advent-of-code/2023/day_03/README.md | 85 ++++++++++ advent-of-code/2023/day_03/build.gradle.kts | 18 +++ advent-of-code/2023/day_03/gradle.properties | 1 + .../2023/day_03/settings.gradle.kts | 2 + .../2023/day_03/src/main/kotlin/Engine.kt | 127 +++++++++++++++ .../2023/day_03/src/main/kotlin/Main.kt | 12 ++ .../src/main/resources}/input.txt | 0 .../2023/day_03/src/test/kotlin/EngineTest.kt | 29 ++++ .../2023/kotlin/src/main/kotlin/Solution03.kt | 147 ------------------ .../kotlin/src/main/resources/day-03/test.txt | 10 -- .../kotlin/src/test/kotlin/Solution03Test.kt | 37 ----- 12 files changed, 317 insertions(+), 194 deletions(-) create mode 100644 advent-of-code/2023/day_03/.gitignore create mode 100644 advent-of-code/2023/day_03/README.md create mode 100644 advent-of-code/2023/day_03/build.gradle.kts create mode 100644 advent-of-code/2023/day_03/gradle.properties create mode 100644 advent-of-code/2023/day_03/settings.gradle.kts create mode 100644 advent-of-code/2023/day_03/src/main/kotlin/Engine.kt create mode 100644 advent-of-code/2023/day_03/src/main/kotlin/Main.kt rename advent-of-code/2023/{kotlin/src/main/resources/day-03 => day_03/src/main/resources}/input.txt (100%) create mode 100644 advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt delete mode 100644 advent-of-code/2023/kotlin/src/main/kotlin/Solution03.kt delete mode 100644 advent-of-code/2023/kotlin/src/main/resources/day-03/test.txt delete mode 100644 advent-of-code/2023/kotlin/src/test/kotlin/Solution03Test.kt diff --git a/advent-of-code/2023/day_03/.gitignore b/advent-of-code/2023/day_03/.gitignore new file mode 100644 index 0000000..6d438dd --- /dev/null +++ b/advent-of-code/2023/day_03/.gitignore @@ -0,0 +1,43 @@ +.gradle +gradle +gradlew* +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Kotlin ### +.kotlin + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/advent-of-code/2023/day_03/README.md b/advent-of-code/2023/day_03/README.md new file mode 100644 index 0000000..5d1371a --- /dev/null +++ b/advent-of-code/2023/day_03/README.md @@ -0,0 +1,85 @@ +# Advent of Code 2023 day 3 solution in Kotlin + +## Day 3: Gear Ratios + +[Task page](https://adventofcode.com/2023/day/3) + +You and the Elf eventually reach a gondola lift station; he says the gondola lift will take you up to the water source, +but this is as far as he can bring you. You go inside. + +It doesn't take long to find the gondolas, but there seems to be a problem: they're not moving. + +"Aaah!" + +You turn around to see a slightly-greasy Elf with a wrench and a look of surprise. "Sorry, I wasn't expecting anyone! +The gondola lift isn't working right now; it'll still be a while before I can fix it." You offer to help. + +The engineer explains that an engine part seems to be missing from the engine, but nobody can figure out which one. +If you can add up all the part numbers in the engine schematic, it should be easy to work out which part is missing. + +The engine schematic (your puzzle input) consists of a visual representation of the engine. +There are lots of numbers and symbols you don't really understand, but apparently any number adjacent to a symbol, +even diagonally, is a "part number" and should be included in your sum. (Periods (.) do not count as a symbol.) + +Here is an example engine schematic: + +``` +467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.. +``` + +In this schematic, two numbers are not part numbers because they are not adjacent to a symbol: 114 (top right) +and 58 (middle right). Every other number is adjacent to a symbol and so is a part number; their sum is 4361. + +Of course, the actual engine schematic is much larger. +What is the sum of all of the part numbers in the engine schematic? + + +## Part Two + +The engineer finds the missing part and installs it in the engine! As the engine springs to life, +you jump in the closest gondola, finally ready to ascend to the water source. + +You don't seem to be going very fast, though. Maybe something is still wrong? Fortunately, +the gondola has a phone labeled "help", so you pick it up and the engineer answers. + +Before you can explain the situation, she suggests that you look out the window. There stands the engineer, +holding a phone in one hand and waving with the other. You're going so slowly that you haven't even left the station. +You exit the gondola. + +The missing part wasn't the only issue - one of the gears in the engine is wrong. +A gear is any * symbol that is adjacent to exactly two part numbers. Its gear ratio is the result +of multiplying those two numbers together. + +This time, you need to find the gear ratio of every gear and add them all up so that the engineer can figure out +which gear needs to be replaced. + +Consider the same engine schematic again: + +``` +467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598.. +``` + +In this schematic, there are two gears. The first is in the top left; it has part numbers 467 and 35, +so its gear ratio is 16345. The second gear is in the lower right; its gear ratio is 451490. +(The * adjacent to 617 is not a gear because it is only adjacent to one part number.) +Adding up all of the gear ratios produces 467835. + +What is the sum of all of the gear ratios in your engine schematic? diff --git a/advent-of-code/2023/day_03/build.gradle.kts b/advent-of-code/2023/day_03/build.gradle.kts new file mode 100644 index 0000000..2c70efe --- /dev/null +++ b/advent-of-code/2023/day_03/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("jvm") version "2.0.21" +} + +group = "space.comfycamp" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/advent-of-code/2023/day_03/gradle.properties b/advent-of-code/2023/day_03/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/advent-of-code/2023/day_03/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/advent-of-code/2023/day_03/settings.gradle.kts b/advent-of-code/2023/day_03/settings.gradle.kts new file mode 100644 index 0000000..e74d30d --- /dev/null +++ b/advent-of-code/2023/day_03/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "day_03" + diff --git a/advent-of-code/2023/day_03/src/main/kotlin/Engine.kt b/advent-of-code/2023/day_03/src/main/kotlin/Engine.kt new file mode 100644 index 0000000..a736a6e --- /dev/null +++ b/advent-of-code/2023/day_03/src/main/kotlin/Engine.kt @@ -0,0 +1,127 @@ +package space.comfycamp + +/** Part 1. */ +fun getSumOfParts(lines: List): Int { + var sum = 0 + var buf = "" + var isBufferAdjacent = false + + for (i in lines.indices) { + for (j in lines[i].indices) { + if (lines[i][j].isDigit()) { + buf += lines[i][j] + + isBufferAdjacent = isBufferAdjacent || getAdjacentSymbols(lines, i, j, ::isSymbol).isNotEmpty() + + // Skip number parsing if it's not the end of string. + if (j < lines[0].length - 1) continue + } + + if (buf.isNotEmpty() && isBufferAdjacent) { + sum += buf.toInt() + } + + buf = "" + isBufferAdjacent = false + } + } + + return sum +} + +/** Part 2. */ +fun getGearRatioSum(lines: List): Int { + // 3d list of adjacent numbers for every asterisk. + val adjacentNumbers = List(lines.size) { + List(lines[0].length) { + mutableListOf() + } + } + + var buf = "" + val adjacentGears = mutableListOf() + + for (i in lines.indices) { + for (j in lines[i].indices) { + if (lines[i][j].isDigit()) { + buf += lines[i][j] + + // Remember every asterisk that is adjacent to the current digit, + for (c in getAdjacentSymbols(lines, i, j, ::isAsterisk)) { + if (!adjacentGears.contains(c)) adjacentGears.add(c) + } + + if (j < lines[0].length - 1) continue + } + + if (buf.isNotEmpty()) { + val num = buf.toInt() + for (gear in adjacentGears) { + // Add current buffer to the list of adjacent numbers. + adjacentNumbers[gear.i][gear.j].add(num) + } + } + + buf = "" + adjacentGears.clear() + } + } + + var sum = 0 + + for (i in adjacentNumbers.indices) { + for (j in adjacentNumbers[i].indices) { + if (adjacentNumbers[i][j].size == 2) { + sum += adjacentNumbers[i][j][0] * adjacentNumbers[i][j][1] + } + } + } + + return sum +} + +private fun getAdjacentSymbols(lines: List, i: Int, j: Int, condition: (Char) -> Boolean): List { + val jMax = lines[0].length - 1 + val res = mutableListOf() + + if (i > 0) { + // Top + if (condition(lines[i - 1][j])) res.add(Coordinates(i - 1, j)) + + // Top left + if (j > 0 && condition(lines[i - 1][j - 1])) res.add(Coordinates(i - 1, j - 1)) + + // Top right + if (j < jMax && condition(lines[i - 1][j + 1])) res.add(Coordinates(i - 1, j + 1)) + } + + if (i < lines.size - 1) { + // Bottom + if (condition(lines[i + 1][j])) res.add(Coordinates(i + 1, j)) + + // Bottom left + if (j > 0 && condition(lines[i + 1][j - 1])) res.add(Coordinates(i + 1, j - 1)) + + // Bottom right + if (j < jMax && condition(lines[i + 1][j + 1])) res.add(Coordinates(i + 1, j + 1)) + } + + // Left + if (j > 0 && condition(lines[i][j - 1])) res.add(Coordinates(i, j - 1)) + + // Right + if (j < jMax && condition(lines[i][j + 1])) res.add(Coordinates(i, j + 1)) + + return res +} + +private fun isSymbol(char: Char): Boolean { + if (char.isDigit()) return false + return char != '.' +} + +private fun isAsterisk(char: Char): Boolean { + return char == '*' +} + +data class Coordinates(var i: Int, var j: Int) diff --git a/advent-of-code/2023/day_03/src/main/kotlin/Main.kt b/advent-of-code/2023/day_03/src/main/kotlin/Main.kt new file mode 100644 index 0000000..1dd6804 --- /dev/null +++ b/advent-of-code/2023/day_03/src/main/kotlin/Main.kt @@ -0,0 +1,12 @@ +package space.comfycamp + +fun main() { + val resource = object{}.javaClass.getResource("/input.txt")!! + val lines = resource.readText().trim().lines() + + val res1 = getSumOfParts(lines) + println("Part 1: $res1") + + val res2 = getGearRatioSum(lines) + println("Part 2: $res2") +} diff --git a/advent-of-code/2023/kotlin/src/main/resources/day-03/input.txt b/advent-of-code/2023/day_03/src/main/resources/input.txt similarity index 100% rename from advent-of-code/2023/kotlin/src/main/resources/day-03/input.txt rename to advent-of-code/2023/day_03/src/main/resources/input.txt diff --git a/advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt b/advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt new file mode 100644 index 0000000..c7f4418 --- /dev/null +++ b/advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt @@ -0,0 +1,29 @@ +import space.comfycamp.getGearRatioSum +import space.comfycamp.getSumOfParts +import kotlin.test.assertEquals +import kotlin.test.Test + +class EngineTest { + val lines = listOf( + "467..114..", + "...*......", + "..35..633.", + "......#...", + "617*......", + ".....+.58.", + "..592.....", + "......755.", + "...$.*....", + ".664.598..", + ) + + @Test + fun testGetSumOfParts() { + assertEquals(4361, getSumOfParts(lines)) + } + + @Test + fun testGetGearRatioSum() { + assertEquals(467835, getGearRatioSum(lines)) + } +} diff --git a/advent-of-code/2023/kotlin/src/main/kotlin/Solution03.kt b/advent-of-code/2023/kotlin/src/main/kotlin/Solution03.kt deleted file mode 100644 index c1c850d..0000000 --- a/advent-of-code/2023/kotlin/src/main/kotlin/Solution03.kt +++ /dev/null @@ -1,147 +0,0 @@ -class Solution03(private val lines: List) { - - /** Maximum value of J = number of columns - 1. - * If j is less than jMax, then j can be safely incremented. - * */ - private val jMax = lines[0].length - 1 - - /** Array to simplify character classification. */ - private val digits = arrayOf('1', '2', '3', '4', '5', '6', '7', '8', '9', '0') - - /** Part 1. */ - fun getSumOfParts(): Int { - var sum = 0 - var buf = "" - var isBufferAdjacent = false - - for (i in lines.indices) { - for (j in lines[i].indices) { - if (digits.contains(lines[i][j])) { - buf += lines[i][j] - - // Mark current buffer as adjacent to a symbol if necessary. - if (getAdjacentSymbols(i, j, ::isSymbol).isNotEmpty()) { - isBufferAdjacent = true - } - - // Skip number parsing if it's not the end of string. - if (j < jMax) continue - } - - if (buf.isNotEmpty() && isBufferAdjacent) { - sum += buf.toInt() - } - - buf = "" - isBufferAdjacent = false - } - } - - return sum - } - - /** Part 2. */ - fun getGearRatioSum(): Int { - /** - * 2d array with a list of adjacent numbers for every asterisk. - */ - val adjacentNumbers = Array(lines.size) { - Array(lines[0].length){ - mutableListOf() - } - } - - var buf = "" - val adjacentGears = mutableListOf() - - for (i in lines.indices) { - for (j in lines[i].indices) { - if (digits.contains(lines[i][j])) { - buf += lines[i][j] - - // For every asterisk that is adjacent to the current digit - for (c in getAdjacentSymbols(i, j, ::isAsterisk)) { - // Check if we already added asterisk to our temporary list - var exists = false - for (gear in adjacentGears) { - if (gear.i == c.i && gear.j == c.j) exists = true - } - - // Add new asterisk - if (!exists) adjacentGears.add(c) - } - - if (j < jMax) continue - } - - if (buf.isNotEmpty()) { - val num = buf.toInt() - for (adjacentGear in adjacentGears) { - // Add current buffer to the list of adjacent numbers. - adjacentNumbers[adjacentGear.i][adjacentGear.j].add(num) - } - } - - buf = "" - adjacentGears.clear() - } - } - - var sum = 0 - - for (i in adjacentNumbers.indices) { - for (j in adjacentNumbers[i].indices) { - if (adjacentNumbers[i][j].size == 2) { - sum += adjacentNumbers[i][j][0] * adjacentNumbers[i][j][1] - } - } - } - - return sum - } - - private fun getAdjacentSymbols(i: Int, j: Int, condition: (i: Int, j: Int) -> Boolean): List { - val res = mutableListOf() - - if (i > 0) { - // Top - if (condition(i-1, j)) res.add(Coordinates(i-1, j)) - - // Top left - if (j > 0 && condition(i-1, j-1)) res.add(Coordinates(i-1, j-1)) - - // Top right - if (j < jMax && condition(i-1, j+1)) res.add(Coordinates(i-1, j+1)) - } - - if (i < lines.size - 1) { - // Bottom - if (condition(i+1, j)) res.add(Coordinates(i+1, j)) - - // Bottom left - if (j > 0 && condition(i+1, j-1)) res.add(Coordinates(i+1, j-1)) - - // Bottom right - if (j < jMax && condition(i+1,j+1)) res.add(Coordinates(i+1, j+1)) - } - - // Left - if (j > 0 && condition(i, j-1)) res.add(Coordinates(i, j-1)) - - // Right - if (j < jMax && condition(i, j+1)) res.add(Coordinates(i, j+1)) - - return res - } - - private fun isSymbol(i: Int, j: Int): Boolean { - if (digits.contains(lines[i][j])) return false - return lines[i][j] != '.' - } - - private fun isAsterisk(i: Int, j: Int): Boolean { - return lines[i][j] == '*' - } -} - -class Coordinates(var i: Int, var j: Int) diff --git a/advent-of-code/2023/kotlin/src/main/resources/day-03/test.txt b/advent-of-code/2023/kotlin/src/main/resources/day-03/test.txt deleted file mode 100644 index b20187f..0000000 --- a/advent-of-code/2023/kotlin/src/main/resources/day-03/test.txt +++ /dev/null @@ -1,10 +0,0 @@ -467..114.. -...*...... -..35..633. -......#... -617*...... -.....+.58. -..592..... -......755. -...$.*.... -.664.598.. diff --git a/advent-of-code/2023/kotlin/src/test/kotlin/Solution03Test.kt b/advent-of-code/2023/kotlin/src/test/kotlin/Solution03Test.kt deleted file mode 100644 index 331d82e..0000000 --- a/advent-of-code/2023/kotlin/src/test/kotlin/Solution03Test.kt +++ /dev/null @@ -1,37 +0,0 @@ -import java.io.File -import kotlin.test.Test -import kotlin.test.assertEquals - -class Solution03Test { - @Test - fun testGetSumOfParts() { - val text = ResourceReader().readFile("day-03/test.txt") - val instance = Solution03(text) - val res = instance.getSumOfParts() - assertEquals(4361, res) - } - - @Test - fun solvePart1() { - val text = ResourceReader().readFile("day-03/input.txt") - val instance = Solution03(text) - val res = instance.getSumOfParts() - println(res) - } - - @Test - fun testGetGearRatio() { - val text = ResourceReader().readFile("day-03/test.txt") - val instance = Solution03(text) - val res = instance.getGearRatioSum() - assertEquals(467835, res) - } - - @Test - fun solvePart2() { - val text = ResourceReader().readFile("day-03/input.txt") - val instance = Solution03(text) - val res = instance.getGearRatioSum() - println(res) - } -}