Day 15: Lens Library
by @sjrd
Puzzle description
https://adventofcode.com/2023/day/15
Solution Summary
- Parse the input into a list of sequences.
- Implement the
HASH
function. - Follow the given algorithm almost 1 for 1.
Part 1
For Part 1, we are asked to implement a string hashing called HASH
.
While it is specified in a very imperative way, it lends itself to a straightforward fold in a functional style:
/** The `HASH` function. */
def hash(sequence: String): Int =
sequence.foldLeft(0) { (prev, c) =>
((prev + c.toInt) * 17) % 256
}
end hash
We also have to parse the input into comma-separated sequences. We are told to ignore newline characters:
/** Parses the input into a list of sequences. */
def inputToSequences(input: String): List[String] =
input.filter(_ != '\n').split(',').toList
Now we wire things together and we are done:
def part1(input: String): String =
val sequences = inputToSequences(input)
val result = sequences.map(hash(_)).sum
println(result)
result.toString()
end part1
Part 2
Part 2 is the real stuff.
The first part was basically unit-testing our HASH
function.
For Part 2, we asked to implement what amounts to put
and remove
on a hash table.
First, we define the LabeledLens
class to hold a lens with a label and a focal length.
We then define our boxes
as an array of 256 lists of LabeledLens
es:
/** A labeled lens, as found in the boxes. */
final case class LabeledLens(label: String, focalLength: Int)
val boxes = Array.fill[List[LabeledLens]](256)(Nil)
We then implement the logical operations removeLens
and addLens
, corresponding to the -
and =
steps.
// Remove the lens with the given label from the box it belongs to
def removeLens(label: String): Unit =
val boxIndex = hash(label)
boxes(boxIndex) = boxes(boxIndex).filter(_.label != label)
// Add a lens in the contents of a box; replace an existing label or add to the end
def addLensToList(lens: LabeledLens, list: List[LabeledLens]): List[LabeledLens] =
list match
case Nil => lens :: Nil // add to the end
case LabeledLens(lens.label, _) :: tail => lens :: tail // replace
case head :: tail => head :: addLensToList(lens, tail) // keep looking
// Add a lens with the given label and focal length into the box it belongs to, in the right place
def addLens(label: String, focalLength: Int): Unit =
val lens = LabeledLens(label, focalLength)
val boxIndex = hash(label)
boxes(boxIndex) = addLensToList(lens, boxes(boxIndex))
Finally, we use our trust s
extractor to parse and "execute" each step of the initialization sequence:
// Parse and execute the steps
for step <- steps do
step match
case s"$label-" => removeLens(label)
case s"$label=$focalLength" => addLens(label, focalLength.toInt)
To prove to our hash table follows the correct algorithm, we are asked to compute the focusing power of our boxes. Here again, we follow the definition of the problem 1 to 1:
// Focusing power of a lens in a given box and at a certain position within that box
def focusingPower(boxIndex: Int, lensIndex: Int, lens: LabeledLens): Int =
(boxIndex + 1) * (lensIndex + 1) * lens.focalLength
// Focusing power of all the lenses
val focusingPowers =
for
(box, boxIndex) <- boxes.zipWithIndex
(lens, lensIndex) <- box.zipWithIndex
yield
focusingPower(boxIndex, lensIndex, lens)
// Sum it up
val result = focusingPowers.sum
Final Code
/** The `HASH` function. */
def hash(sequence: String): Int =
sequence.foldLeft(0) { (prev, c) =>
((prev + c.toInt) * 17) % 256
}
end hash
/** Parses the input into a list of sequences. */
def inputToSequences(input: String): List[String] =
input.filter(_ != '\n').split(',').toList
def part1(input: String): String =
val sequences = inputToSequences(input)
val result = sequences.map(hash(_)).sum
println(result)
result.toString()
end part1
/** A labeled lens, as found in the boxes. */
final case class LabeledLens(label: String, focalLength: Int)
def part2(input: String): String =
val steps = inputToSequences(input)
val boxes = Array.fill[List[LabeledLens]](256)(Nil)
// --- Processing all the steps --------------------
// Remove the lens with the given label from the box it belongs to
def removeLens(label: String): Unit =
val boxIndex = hash(label)
boxes(boxIndex) = boxes(boxIndex).filter(_.label != label)
// Add a lens in the contents of a box; replace an existing label or add to the end
def addLensToList(lens: LabeledLens, list: List[LabeledLens]): List[LabeledLens] =
list match
case Nil => lens :: Nil // add to the end
case LabeledLens(lens.label, _) :: tail => lens :: tail // replace
case head :: tail => head :: addLensToList(lens, tail) // keep looking
// Add a lens with the given label and focal length into the box it belongs to, in the right place
def addLens(label: String, focalLength: Int): Unit =
val lens = LabeledLens(label, focalLength)
val boxIndex = hash(label)
boxes(boxIndex) = addLensToList(lens, boxes(boxIndex))
// Parse and execute the steps
for step <- steps do
step match
case s"$label-" => removeLens(label)
case s"$label=$focalLength" => addLens(label, focalLength.toInt)
// --- Computing the focusing power --------------------
// Focusing power of a lens in a given box and at a certain position within that box
def focusingPower(boxIndex: Int, lensIndex: Int, lens: LabeledLens): Int =
(boxIndex + 1) * (lensIndex + 1) * lens.focalLength
// Focusing power of all the lenses
val focusingPowers =
for
(box, boxIndex) <- boxes.zipWithIndex
(lens, lensIndex) <- box.zipWithIndex
yield
focusingPower(boxIndex, lensIndex, lens)
// Sum it up
val result = focusingPowers.sum
result.toString()
end part2
Run it in the browser
Part 1
Part 2
Solutions from the community
- Solution by jnclt
- Solution by Spamegg
- Solution by YannMoisan
- Solution by Rui Alves
- Solution by g.berezin
- Solution by Joel Edwards
- Solution by Jamie Thompson
- Solution by Paweł Cembaluk
Share your solution to the Scala community by editing this page. You can even write the whole article! See here for the expected format