Skip to content

Commit

Permalink
2023, Day 03
Browse files Browse the repository at this point in the history
  • Loading branch information
jurisk committed Dec 3, 2023
1 parent bfc4dc4 commit 3b2bea2
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 7 deletions.
10 changes: 10 additions & 0 deletions scala2/src/main/resources/2023/03-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
140 changes: 140 additions & 0 deletions scala2/src/main/resources/2023/03.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ object Advent17 {
}

private def printField(name: String, field: Field2D[Square]): Unit =
Field2D.printField[Square](name, field, _.toChar)
Field2D.printField[Square](name.some, field, _.toChar)

def simulate(field: Field2D[Square]): Field2D[Square] = {
printField("Before:", field)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ object Advent24 {

val result = charField.updatedAtUnsafe(state.location, 'E')

Field2D.printField[Char](s"${state.time} time:", result, identity)
Field2D.printField[Char](s"${state.time} time:".some, result, identity)
}

private def solve[T <: State[T]](maze: Maze, startF: Maze => T): Int = {
Expand Down
116 changes: 116 additions & 0 deletions scala2/src/main/scala/jurisk/adventofcode/y2023/Advent03.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package jurisk.adventofcode.y2023

import jurisk.algorithms.pathfinding.ConnectedComponents
import jurisk.geometry.{Coords2D, Field2D}
import jurisk.utils.FileInput._

object Advent03 {
import Square._

sealed trait Square {
def toChar: Char
}

object Square {
final case class Digit(value: Int) extends Square {
override def toChar: Char = ('0'.toInt + value).toChar
}

final case class Symbol(value: Char) extends Square {
override def toChar: Char = value
}

final case object Empty extends Square {
override def toChar: Char = '.'
}

val Gear: Symbol = Symbol('*')

def parse(ch: Char): Square =
ch match {
case '.' => Empty
case d if d.isDigit => Digit(d - '0')
case _ => Symbol(ch)
}
}

type Parsed = Field2D[Square]

def parse(input: String): Parsed =
Field2D.parseFromString(input, Square.parse)

private def extractNumbers(
field: Field2D[Square]
): List[(Int, Set[Coords2D])] = {
def isDigit(square: Square) = square match {
case Digit(_) => true
case _ => false
}

val digitCoords = field.filterCoordsByValue(isDigit)

ConnectedComponents
.connectedComponents[Coords2D](
digitCoords,
field.adjacent8Where(_, isDigit),
)
.map { island =>
val number = island.toList
.sortBy(_.x)
.map { c =>
field(c).toChar
}
.mkString
.toInt
(number, island)
}
.toList
}

def part1(data: Parsed): Int = {
val numbers = extractNumbers(data)
val specialSymbolCoords = data.filterCoordsByValue {
case Symbol(_) => true
case _ => false
}.toSet

numbers.collect {
case (number, coords)
if (coords.flatMap(
data.adjacent8
) intersect specialSymbolCoords).nonEmpty =>
number
}.sum
}

def part2(data: Parsed): Int = {
val numbers = extractNumbers(data)
val gearSymbolCoords = data.filterCoordsByValue(_ == Gear)

gearSymbolCoords
.map { gearCoordinates =>
val catchment = data.adjacent8(gearCoordinates).toSet
val gearNumbers = numbers
.collect {
case (number, numberSquareCoordinates)
if (catchment intersect numberSquareCoordinates).nonEmpty =>
number
}

gearNumbers
}
.filter(_.length == 2)
.map(_.product)
.sum
}

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

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

println(s"Part 1: ${part1(realData)}")
println(s"Part 2: ${part2(realData)}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package jurisk.algorithms.pathfinding

object ConnectedComponents {
def connectedComponents[N](
starts: List[N],
successors: N => List[N],
): Set[Set[N]] = {
var results: Set[Set[N]] = Set.empty
var visited: Set[N] = Set.empty

starts foreach { n =>
if (!visited.contains(n)) {
val island = Bfs.bfsReachable(n, successors).toSet
results += island
visited = visited ++ island
}
}

results
}
}
26 changes: 21 additions & 5 deletions scala2/src/main/scala/jurisk/geometry/Field2D.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cats.Functor
import cats.implicits.toFunctorOps

final case class Field2D[T](
data: Vector[Vector[T]],
private val data: Vector[Vector[T]],
topLeft: Coords2D = Coords2D.Zero,
) {
val width: Int = data.head.length
Expand Down Expand Up @@ -46,7 +46,7 @@ final case class Field2D[T](
.flatMap(_.lift(c.x - topLeft.x))

def apply(c: Coords2D): T =
at(c).getOrElse(sys.error(s"Coords2D $c are invalid"))
at(c) getOrElse sys.error(s"Coords2D $c are invalid")

def updatedAtUnsafe(c: Coords2D, newValue: T): Field2D[T] = {
val yIdx = c.y - topLeft.y
Expand Down Expand Up @@ -74,12 +74,22 @@ final case class Field2D[T](
def adjacent4(c: Coords2D): List[Coords2D] =
neighboursFor(c, includeDiagonal = false)

def adjacent4Where(c: Coords2D, predicate: T => Boolean): List[Coords2D] =
adjacent4(c) filter { n =>
get(n) exists predicate
}

def adjacent4Values(c: Coords2D): List[T] =
adjacent4(c).flatMap(at)

def adjacent8(c: Coords2D): List[Coords2D] =
neighboursFor(c, includeDiagonal = true)

def adjacent8Where(c: Coords2D, predicate: T => Boolean): List[Coords2D] =
adjacent8(c) filter { n =>
get(n) exists predicate
}

def adjacent8Values(c: Coords2D): List[T] =
adjacent8(c).flatMap(at)

Expand Down Expand Up @@ -108,6 +118,12 @@ final case class Field2D[T](
Coords2D(x, y)
}

def rows: List[Vector[T]] = data.toList

def columns: List[Vector[T]] = for {
columnIdx <- (0 until width).toList
} yield column(columnIdx)

def column(x: Int): Vector[T] = data.map(_(x - topLeft.x))
def coordsForColumn(x: Int): List[Coords2D] = yIndices.toList map { y =>
Coords2D(x, y)
Expand All @@ -120,7 +136,7 @@ final case class Field2D[T](
def lastColumnValues: Vector[T] = column(width - 1)

def count(p: T => Boolean): Int =
values.count(p)
values count p

def createSuccessorsFunction(
canGoPredicate: (T, T) => Boolean,
Expand Down Expand Up @@ -168,11 +184,11 @@ object Field2D {
)

def printField[T](
intro: String,
intro: Option[String],
field: Field2D[T],
toChar: T => Char,
): Unit = {
println(intro)
intro foreach println
val charField = field.map(toChar)
val representation = Field2D.toDebugRepresentation(charField)
println(representation)
Expand Down
23 changes: 23 additions & 0 deletions scala2/src/test/scala/jurisk/adventofcode/y2023/Advent03Spec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package jurisk.adventofcode.y2023

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers._
import Advent03._

class Advent03Spec extends AnyFlatSpec {
"Advent00" should "test part 1" in {
part1(parseFile("2023/03-test.txt")) shouldEqual 4361
}

it should "real part 1" in {
part1(parseFile("2023/03.txt")) shouldEqual 533784
}

it should "test part 2" in {
part2(parseFile("2023/03-test.txt")) shouldEqual 467835
}

it should "real part 2" in {
part2(parseFile("2023/03.txt")) shouldEqual 78826761
}
}

0 comments on commit 3b2bea2

Please sign in to comment.