Skip to content

Commit

Permalink
2023-15 (#11)
Browse files Browse the repository at this point in the history
* 2023-15

* 2023-15

* 2023-15

* 2023-15

---------

Co-authored-by: Juris <jurisk@users.noreply.github.com>
  • Loading branch information
jurisk and jurisk authored Dec 15, 2023
1 parent ac75875 commit 765ec66
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 15 deletions.
1 change: 1 addition & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
| 2023-12 | [Hot Springs](https://adventofcode.com/2023/day/12) | [Scala](scala2/src/main/scala/jurisk/adventofcode/y2023/Advent12.scala) | | |
| 2023-13 | [Point of Incidence](https://adventofcode.com/2023/day/13) | [Scala](scala2/src/main/scala/jurisk/adventofcode/y2023/Advent13.scala) | | |
| 2023-14 | [Parabolic Reflector Dish](https://adventofcode.com/2023/day/14) | [Scala](scala2/src/main/scala/jurisk/adventofcode/y2023/Advent14.scala) | | |
| 2023-15 | [Lens Library](https://adventofcode.com/2023/day/15) | [Scala](scala2/src/main/scala/jurisk/adventofcode/y2023/Advent15.scala) | | |
| 2022-01 | [Calorie Counting](https://adventofcode.com/2022/day/1) | [Scala](scala2/src/main/scala/jurisk/adventofcode/y2022/Advent01.scala) | | |
| 2022-02 | [Rock Paper Scissors](https://adventofcode.com/2022/day/2) | [Scala](scala2/src/main/scala/jurisk/adventofcode/y2022/Advent02.scala) | | |
| 2022-03 | [Rucksack Reorganization](https://adventofcode.com/2022/day/3) | [Scala](scala2/src/main/scala/jurisk/adventofcode/y2022/Advent03.scala) | | |
Expand Down
1 change: 1 addition & 0 deletions scala2/src/main/resources/2023/15-test.txt
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
1 change: 1 addition & 0 deletions scala2/src/main/resources/2023/15.txt

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions scala2/src/main/scala/jurisk/adventofcode/y2023/Advent14.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,22 @@ object Advent14 {
// `rotation` - the rotation needed to be applied to `data` so that the sliding becomes to the West (left)
private def slideHelper(data: Input, rotation: Rotation): Input = {
def slideRowLeft(row: Vector[Square]): Vector[Square] = {
def slideHelper(row: List[Square], emptiesToAdd: Int): List[Square] = {
def f(row: List[Square], emptiesToAdd: Int): List[Square] = {
import List.fill

row match {
case Empty :: tail => slideHelper(tail, emptiesToAdd + 1)
case Round :: tail => Round :: slideHelper(tail, emptiesToAdd)
case Empty :: tail =>
f(tail, emptiesToAdd + 1)
case Round :: tail =>
Round :: f(tail, emptiesToAdd)
case Cube :: tail =>
fill(emptiesToAdd)(Empty) ::: Cube :: slideHelper(tail, 0)
case Nil => fill(emptiesToAdd)(Empty)
fill(emptiesToAdd)(Empty) ::: Cube :: f(tail, 0)
case Nil =>
fill(emptiesToAdd)(Empty)
}
}

slideHelper(row.toList, 0).toVector
f(row.toList, 0).toVector
}

val rotated = data rotate rotation
Expand Down
104 changes: 104 additions & 0 deletions scala2/src/main/scala/jurisk/adventofcode/y2023/Advent15.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package jurisk.adventofcode.y2023

import jurisk.adventofcode.y2023.Advent15.Step.{Remove, Replace}
import jurisk.utils.CollectionOps.{SeqOps, VectorOps}
import jurisk.utils.FileInput._
import jurisk.utils.Parsing.StringOps

object Advent15 {
private type Steps = List[Step]
private type Label = String
private type FocalLength = Int

def calculateHash(s: String): Int =
s.toList.foldLeft(0) { case (acc, ch) =>
((acc + ch.toInt) * 17) % 256
}

def parse(input: String): Steps =
input.split(",").map(Step.parse).toList

sealed trait Step {
def originalString: String
}

object Step {
final case class Remove(label: String) extends Step {
override def originalString: Label = s"$label-"
}

final case class Replace(
label: Label,
focalLength: FocalLength,
) extends Step {
override def originalString: Label = s"$label=$focalLength"
}

def parse(s: String): Step =
s match {
case s"$label-" => Remove(label)
case s"$label=$focalLength" => Replace(label, focalLength.toInt)
case _ => s.failedToParse
}
}

final case class Lens(label: String, focalLength: Int)

final case class LensBox(lenses: Vector[Lens]) {
def subtract(label: Label): LensBox =
lenses.firstIndexWhere(_.label == label) match {
case Some(idx) => LensBox(lenses.removeAt(idx))
case None => this
}

def replace(label: Label, value: FocalLength): LensBox =
LensBox(
lenses.firstIndexWhere(_.label == label) match {
case Some(idx) => lenses.updated(idx, Lens(label, value))
case None => lenses :+ Lens(label, value)
}
)

def value: Int =
lenses.zipWithIndex.map { case (lens, index) =>
(index + 1) * lens.focalLength
}.sum
}

private object LensBox {
def empty: LensBox = LensBox(Vector.empty)
}

def part1(data: Steps): Int =
data.map(op => calculateHash(op.originalString)).sum

def part2(ops: Steps): Int = {
val LensCount = 256
val boxen = ops.foldLeft(Vector.fill(LensCount)(LensBox.empty)) {
case (acc, op) =>
op match {
case Remove(label) =>
val idx = calculateHash(label)
acc.updatedWith(idx)(_.subtract(label))

case Replace(label, focalLength) =>
val idx = calculateHash(label)
acc.updatedWith(idx)(_.replace(label, focalLength))
}
}

boxen.zipWithIndex.map { case (contents, idx) =>
(idx + 1) * contents.value
}.sum
}

def parseFile(fileName: String): Steps =
parse(readFileText(fileName))

def main(args: Array[String]): Unit = {
val realData: Steps = parseFile("2023/15.txt")

println(s"Part 1: ${part1(realData)}")
println(s"Part 2: ${part2(realData)}")
}
}
40 changes: 31 additions & 9 deletions scala2/src/main/scala/jurisk/utils/CollectionOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,43 @@ object CollectionOps {
List.fill(times)(list).flatten
}

implicit class IterableOps[T](seq: Iterable[T]) {
def counts: Map[T, Int] = seq.groupMapReduce(identity)(_ => 1)(_ + _)
implicit class SeqOps[T](seq: Seq[T]) {
def firstIndexWhere(p: T => Boolean, from: Int = 0): Option[Int] = {
val result = seq.indexWhere(p, from)
if (result == -1) {
none
} else {
result.some
}
}
}

implicit class IterableOps[T](iterable: Iterable[T]) {
def counts: Map[T, Int] = iterable.groupMapReduce(identity)(_ => 1)(_ + _)

def consecutiveGroupCounts: List[(T, Int)] =
seq.headOption match {
iterable.headOption match {
case Some(head) =>
val (taken, remaining) = seq.span(_ == head)
val (taken, remaining) = iterable.span(_ == head)
(head -> taken.size) :: remaining.consecutiveGroupCounts

case None =>
Nil
}

def allDistinct: Boolean = seq.toSet.size == seq.size
def allDistinct: Boolean = iterable.toSet.size == iterable.size

def singleElementUnsafe: T =
if (seq.size == 1) seq.head
if (iterable.size == 1) iterable.head
else
s"Expected a single element, but got ${seq.toList.mkString("(", ", ", ")")}".fail
s"Expected a single element, but got ${iterable.toList.mkString("(", ", ", ")")}".fail

// We just keep mistyping this so much, we may as well add it :shrug:
def singleResultUnsafe: T = singleElementUnsafe

def twoElementsUnsafe: (T, T) =
if (seq.size == 2) (seq.head, seq.tail.head)
else s"Expected two elements, but got $seq".fail
if (iterable.size == 2) (iterable.head, iterable.tail.head)
else s"Expected two elements, but got $iterable".fail
}

implicit class EqIterableOps[T: Eq](seq: Iterable[T]) {
Expand Down Expand Up @@ -106,4 +117,15 @@ object CollectionOps {
case Nil => Nil
}
}

implicit class VectorOps[T](val vector: Vector[T]) extends AnyVal {
def updatedWith(index: Int)(modify: T => T): Vector[T] =
vector.lift(index) match {
case Some(currentValue) => vector.updated(index, modify(currentValue))
case None => vector
}

def removeAt(index: Int): Vector[T] =
vector.slice(0, index) ++ vector.slice(index + 1, vector.length)
}
}
37 changes: 37 additions & 0 deletions scala2/src/test/scala/jurisk/adventofcode/y2023/Advent15Spec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package jurisk.adventofcode.y2023

import org.scalatest.freespec.AnyFreeSpec
import Advent15._
import org.scalatest.matchers.should.Matchers._

class Advent15Spec extends AnyFreeSpec {
"calculateHash" - {
"HASH" in {
calculateHash("HASH") shouldEqual 52
}

"rn=1" in {
calculateHash("rn=1") shouldEqual 30
}
}

"part 1" - {
"test" in {
part1(parseFile("2023/15-test.txt")) shouldEqual 1320
}

"real" in {
part1(parseFile("2023/15.txt")) shouldEqual 521341
}
}

"part 2" - {
"test" in {
part2(parseFile("2023/15-test.txt")) shouldEqual 145
}

"real" in {
part2(parseFile("2023/15.txt")) shouldEqual 252782
}
}
}

0 comments on commit 765ec66

Please sign in to comment.