Refactor day 3 of AoC 2023
This commit is contained in:
parent
8a9f518b5b
commit
827d6bf267
12 changed files with 317 additions and 194 deletions
43
advent-of-code/2023/day_03/.gitignore
vendored
Normal file
43
advent-of-code/2023/day_03/.gitignore
vendored
Normal file
|
@ -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
|
85
advent-of-code/2023/day_03/README.md
Normal file
85
advent-of-code/2023/day_03/README.md
Normal file
|
@ -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?
|
18
advent-of-code/2023/day_03/build.gradle.kts
Normal file
18
advent-of-code/2023/day_03/build.gradle.kts
Normal file
|
@ -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()
|
||||
}
|
1
advent-of-code/2023/day_03/gradle.properties
Normal file
1
advent-of-code/2023/day_03/gradle.properties
Normal file
|
@ -0,0 +1 @@
|
|||
kotlin.code.style=official
|
2
advent-of-code/2023/day_03/settings.gradle.kts
Normal file
2
advent-of-code/2023/day_03/settings.gradle.kts
Normal file
|
@ -0,0 +1,2 @@
|
|||
rootProject.name = "day_03"
|
||||
|
127
advent-of-code/2023/day_03/src/main/kotlin/Engine.kt
Normal file
127
advent-of-code/2023/day_03/src/main/kotlin/Engine.kt
Normal file
|
@ -0,0 +1,127 @@
|
|||
package space.comfycamp
|
||||
|
||||
/** Part 1. */
|
||||
fun getSumOfParts(lines: List<String>): 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<String>): Int {
|
||||
// 3d list of adjacent numbers for every asterisk.
|
||||
val adjacentNumbers = List(lines.size) {
|
||||
List(lines[0].length) {
|
||||
mutableListOf<Int>()
|
||||
}
|
||||
}
|
||||
|
||||
var buf = ""
|
||||
val adjacentGears = mutableListOf<Coordinates>()
|
||||
|
||||
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<String>, i: Int, j: Int, condition: (Char) -> Boolean): List<Coordinates> {
|
||||
val jMax = lines[0].length - 1
|
||||
val res = mutableListOf<Coordinates>()
|
||||
|
||||
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)
|
12
advent-of-code/2023/day_03/src/main/kotlin/Main.kt
Normal file
12
advent-of-code/2023/day_03/src/main/kotlin/Main.kt
Normal file
|
@ -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")
|
||||
}
|
29
advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt
Normal file
29
advent-of-code/2023/day_03/src/test/kotlin/EngineTest.kt
Normal file
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
class Solution03(private val lines: List<String>) {
|
||||
|
||||
/** 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<Int>()
|
||||
}
|
||||
}
|
||||
|
||||
var buf = ""
|
||||
val adjacentGears = mutableListOf<Coordinates>()
|
||||
|
||||
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<Coordinates> {
|
||||
val res = mutableListOf<Coordinates>()
|
||||
|
||||
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)
|
|
@ -1,10 +0,0 @@
|
|||
467..114..
|
||||
...*......
|
||||
..35..633.
|
||||
......#...
|
||||
617*......
|
||||
.....+.58.
|
||||
..592.....
|
||||
......755.
|
||||
...$.*....
|
||||
.664.598..
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue