-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add puzzle solution for 2023, day 15
- Loading branch information
Showing
18 changed files
with
540 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
plugins { | ||
val kotlinVersion: String by System.getProperties() | ||
val koverVersion: String by System.getProperties() | ||
|
||
kotlin("jvm") version kotlinVersion | ||
id("org.jetbrains.kotlinx.kover") version koverVersion | ||
} | ||
|
||
dependencies { | ||
val assertjVersion: String by properties | ||
val junitJupiterVersion: String by properties | ||
val junitPlatformVersion: String by properties | ||
|
||
implementation(project(":common:number")) | ||
testImplementation("org.assertj:assertj-core:$assertjVersion") | ||
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") | ||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") | ||
testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitPlatformVersion") | ||
} |
27 changes: 27 additions & 0 deletions
27
...day15/hash/src/main/kotlin/com/curtislb/adventofcode/year2023/day15/hash/HashAlgorithm.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.curtislb.adventofcode.year2023.day15.hash | ||
|
||
import com.curtislb.adventofcode.common.number.modMultiply | ||
|
||
/** | ||
* An implementation of the HASH algorithm, which converts any string to an integer in the range | ||
* `0..<modulus`. | ||
* | ||
* @property modulus The modulus applied to each intermediate value of the HASH algorithm. | ||
* | ||
* @constructor Creates a new instance of [HashAlgorithm] with the given [modulus]. | ||
*/ | ||
class HashAlgorithm(private val modulus: Int) { | ||
/** | ||
* Returns the result of applying the HASH algorithm to the given [string]. | ||
*/ | ||
fun convert(string: String): Int = string.fold(0) { acc, char -> | ||
(acc + char.code).modMultiply(FACTOR, modulus) | ||
} | ||
|
||
companion object { | ||
/** | ||
* A multiplicative factor applied to each intermediate value of the HASH algorithm. | ||
*/ | ||
private const val FACTOR = 17 | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
...3/day15/hash/src/main/kotlin/com/curtislb/adventofcode/year2023/day15/hash/HashmapStep.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.curtislb.adventofcode.year2023.day15.hash | ||
|
||
/** | ||
* A single step in the full HASHMAP process for arranging a sequence of labeled lenses. | ||
* | ||
* @property label The label of the lens on which the step operates. | ||
* @property operation The operation that the step performs on the lens. | ||
*/ | ||
data class HashmapStep(val label: String, val operation: Operation) { | ||
companion object { | ||
/** | ||
* A regex used to parse the lens label and operation info from a HASHMAP step string. | ||
*/ | ||
private val STEP_REGEX = Regex("""(\w+)([\-=])(\d*)""") | ||
|
||
/** | ||
* Returns a [HashmapStep] with a lens label and operation info read from the given string. | ||
* | ||
* The [string] must have one of the following formats: | ||
* | ||
* - `"$label-"`: Perform the [Operation.Dash] operation on the lens with the given `label`. | ||
* - `"$label=$focalLength"`: Perform the [Operation.Equals] operation with the specified | ||
* `focalLength` on the lens with the given `label`. | ||
* | ||
* @throws IllegalArgumentException If [string] is formatted incorrectly. | ||
*/ | ||
fun fromString(string: String): HashmapStep { | ||
val matchResult = STEP_REGEX.matchEntire(string) | ||
require(matchResult != null) { "Malformed HASHMAP step string: $string" } | ||
|
||
// Parse label and operation info from the string | ||
val (label, operationString, operationArgument) = matchResult.destructured | ||
val operation = when (operationString) { | ||
"-" -> Operation.Dash | ||
"=" -> Operation.Equals(operationArgument.toInt()) | ||
else -> error("Invalid operation string: $operationString") | ||
} | ||
|
||
return HashmapStep(label, operation) | ||
} | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
...5/hash/src/main/kotlin/com/curtislb/adventofcode/year2023/day15/hash/LensConfiguration.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package com.curtislb.adventofcode.year2023.day15.hash | ||
|
||
/** | ||
* An arrangement of lenses, with various labels and focal lengths, into an ordered sequence of | ||
* boxes. | ||
* | ||
* @param boxCount The number of boxes into which lenses can be arranged. | ||
* | ||
* @constructor Creates a new instance of [LensConfiguration] with the given `boxCount`. | ||
*/ | ||
class LensConfiguration(boxCount: Int) { | ||
/** | ||
* A sequence of boxes that makes up the current lens configuration. | ||
* | ||
* Each box is represented by an ordered [LinkedHashMap] from the label of each lens in that box | ||
* to its focal length. | ||
*/ | ||
private val boxes: Array<LinkedHashMap<String, Int>> = Array(boxCount) { linkedMapOf() } | ||
|
||
/** | ||
* The HASH algorithm used to convert each lens label to the index of its corresponding box. | ||
*/ | ||
private val hashAlgorithm: HashAlgorithm = HashAlgorithm(boxCount) | ||
|
||
/** | ||
* Returns the focusing power of all the lenses in the current configuration. | ||
* | ||
* The focusing power is given by multiplying the index (starting from 1) of each box by the sum | ||
* of the focal length of each lens in the box multiplied by its index (also from 1) within the | ||
* box. | ||
*/ | ||
fun findFocusingPower(): Int = boxes.withIndex().sumOf { (boxIndex, box) -> | ||
val boxPower = box.values.withIndex().sumOf { (valueIndex, value) -> | ||
(valueIndex + 1) * value | ||
} | ||
(boxIndex + 1) * boxPower | ||
} | ||
|
||
/** | ||
* Updates the current lens arrangement by performing the specified HASHMAP process [step]. | ||
*/ | ||
fun performStep(step: HashmapStep) { | ||
val boxIndex = hashAlgorithm.convert(step.label) | ||
val box = boxes[boxIndex] | ||
when (step.operation) { | ||
Operation.Dash -> box.remove(step.label) | ||
is Operation.Equals -> box[step.label] = step.operation.focalLength | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...023/day15/hash/src/main/kotlin/com/curtislb/adventofcode/year2023/day15/hash/Operation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.curtislb.adventofcode.year2023.day15.hash | ||
|
||
/** | ||
* An operation that may be performed on a lens as part of the HASHMAP process. | ||
*/ | ||
sealed interface Operation { | ||
/** | ||
* A HASHMAP operation that corresponds to removing a lens (of any focal length) from its | ||
* assigned box. | ||
*/ | ||
data object Dash : Operation | ||
|
||
/** | ||
* A HASHMAP operation that corresponds to placing a lens with the specified focal length in its | ||
* assigned box, replacing any existing lens with the same label in the box. | ||
* | ||
* @property focalLength The focal length of the lens to place in its assigned box. | ||
* | ||
* @constructor Creates a new instance of the [Equals] operation with the given [focalLength]. | ||
*/ | ||
data class Equals(val focalLength: Int) : Operation | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
plugins { | ||
val kotlinVersion: String by System.getProperties() | ||
val koverVersion: String by System.getProperties() | ||
|
||
application | ||
kotlin("jvm") version kotlinVersion | ||
id("org.jetbrains.kotlinx.kover") version koverVersion | ||
} | ||
|
||
application { | ||
mainClass.set("com.curtislb.adventofcode.year2023.day15.part1.Year2023Day15Part1Kt") | ||
} | ||
|
||
dependencies { | ||
val assertjVersion: String by properties | ||
val junitJupiterVersion: String by properties | ||
val junitPlatformVersion: String by properties | ||
|
||
implementation(project(":year2023:day15:hash")) | ||
testImplementation("org.assertj:assertj-core:$assertjVersion") | ||
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") | ||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") | ||
testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitPlatformVersion") | ||
} |
116 changes: 116 additions & 0 deletions
116
...art1/src/main/kotlin/com/curtislb/adventofcode/year2023/day15/part1/Year2023Day15Part1.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
--- Day 15: Lens Library --- | ||
The newly-focused parabolic reflector dish is sending all of the collected light to a point on the | ||
side of yet another mountain - the largest mountain on Lava Island. As you approach the mountain, | ||
you find that the light is being collected by the wall of a large facility embedded in the | ||
mountainside. | ||
You find a door under a large sign that says "Lava Production Facility" and next to a smaller sign | ||
that says "Danger - Personal Protective Equipment required beyond this point". | ||
As you step inside, you are immediately greeted by a somewhat panicked reindeer wearing goggles and | ||
a loose-fitting hard hat. The reindeer leads you to a shelf of goggles and hard hats (you quickly | ||
find some that fit) and then further into the facility. At one point, you pass a button with a faint | ||
snout mark and the label "PUSH FOR HELP". No wonder you were loaded into that trebuchet so quickly! | ||
You pass through a final set of doors surrounded with even more warning signs and into what must be | ||
the room that collects all of the light from outside. As you admire the large assortment of lenses | ||
available to further focus the light, the reindeer brings you a book titled "Initialization Manual". | ||
"Hello!", the book cheerfully begins, apparently unaware of the concerned reindeer reading over your | ||
shoulder. "This procedure will let you bring the Lava Production Facility online - all without | ||
burning or melting anything unintended!" | ||
"Before you begin, please be prepared to use the Holiday ASCII String Helper algorithm (appendix | ||
1A)." You turn to appendix 1A. The reindeer leans closer with interest. | ||
The HASH algorithm is a way to turn any string of characters into a single number in the range 0 to | ||
255. To run the HASH algorithm on a string, start with a current value of 0. Then, for each | ||
character in the string starting from the beginning: | ||
- Determine the ASCII code for the current character of the string. | ||
- Increase the current value by the ASCII code you just determined. | ||
- Set the current value to itself multiplied by 17. | ||
- Set the current value to the remainder of dividing itself by 256. | ||
After following these steps for each character in the string in order, the current value is the | ||
output of the HASH algorithm. | ||
So, to find the result of running the HASH algorithm on the string HASH: | ||
- The current value starts at 0. | ||
- The first character is H; its ASCII code is 72. | ||
- The current value increases to 72. | ||
- The current value is multiplied by 17 to become 1224. | ||
- The current value becomes 200 (the remainder of 1224 divided by 256). | ||
- The next character is A; its ASCII code is 65. | ||
- The current value increases to 265. | ||
- The current value is multiplied by 17 to become 4505. | ||
- The current value becomes 153 (the remainder of 4505 divided by 256). | ||
- The next character is S; its ASCII code is 83. | ||
- The current value increases to 236. | ||
- The current value is multiplied by 17 to become 4012. | ||
- The current value becomes 172 (the remainder of 4012 divided by 256). | ||
- The next character is H; its ASCII code is 72. | ||
- The current value increases to 244. | ||
- The current value is multiplied by 17 to become 4148. | ||
- The current value becomes 52 (the remainder of 4148 divided by 256). | ||
So, the result of running the HASH algorithm on the string HASH is 52. | ||
The initialization sequence (your puzzle input) is a comma-separated list of steps to start the Lava | ||
Production Facility. Ignore newline characters when parsing the initialization sequence. To verify | ||
that your HASH algorithm is working, the book offers the sum of the result of running the HASH | ||
algorithm on each step in the initialization sequence. | ||
For example: | ||
``` | ||
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 | ||
``` | ||
This initialization sequence specifies 11 individual steps; the result of running the HASH algorithm | ||
on each of the steps is as follows: | ||
- rn=1 becomes 30. | ||
- cm- becomes 253. | ||
- qp=3 becomes 97. | ||
- cm=2 becomes 47. | ||
- qp- becomes 14. | ||
- pc=4 becomes 180. | ||
- ot=9 becomes 9. | ||
- ab=5 becomes 197. | ||
- pc- becomes 48. | ||
- pc=6 becomes 214. | ||
- ot=7 becomes 231. | ||
In this example, the sum of these results is 1320. Unfortunately, the reindeer has stolen the page | ||
containing the expected verification number and is currently running around the facility with it | ||
excitedly. | ||
Run the HASH algorithm on each step in the initialization sequence. What is the sum of the results? | ||
(The initialization sequence is one long line; be careful when copy-pasting it.) | ||
*/ | ||
|
||
package com.curtislb.adventofcode.year2023.day15.part1 | ||
|
||
import com.curtislb.adventofcode.year2023.day15.hash.HashAlgorithm | ||
import java.nio.file.Path | ||
import java.nio.file.Paths | ||
|
||
/** | ||
* Returns the solution to the puzzle for 2023, day 15, part 1. | ||
* | ||
* @param inputPath The path to the input file for this puzzle. | ||
* @param modulus The modulus applied to each intermediate value of the HASH algorithm. | ||
*/ | ||
fun solve(inputPath: Path = Paths.get("..", "input", "input.txt"), modulus: Int = 256): Int { | ||
val stepStrings = inputPath.toFile().readText().trim().split(",") | ||
val hashAlgorithm = HashAlgorithm(modulus) | ||
return stepStrings.sumOf { hashAlgorithm.convert(it) } | ||
} | ||
|
||
fun main() { | ||
println(solve()) | ||
} |
22 changes: 22 additions & 0 deletions
22
.../src/test/kotlin/com/curtislb/adventofcode/year2023/day15/part1/Year2023Day15Part1Test.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.curtislb.adventofcode.year2023.day15.part1 | ||
|
||
import java.nio.file.Paths | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
|
||
/** | ||
* Tests the solution to the puzzle for 2023, day 15, part 1. | ||
*/ | ||
class Year2023Day15Part1Test { | ||
@Test | ||
fun solve_withRealInput() { | ||
val solution = solve() | ||
assertThat(solution).isEqualTo(506437) | ||
} | ||
|
||
@Test | ||
fun solve_withTestInput() { | ||
val solution = solve(inputPath = Paths.get("..", "input", "test_input.txt")) | ||
assertThat(solution).isEqualTo(1320) | ||
} | ||
} |
Oops, something went wrong.