Skip to content

Commit

Permalink
2023, Day 10 - implemented version using Pick's theorem and Shoelace …
Browse files Browse the repository at this point in the history
…formula
  • Loading branch information
jurisk committed Dec 10, 2023
1 parent 07950ba commit 1024c9c
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 25 deletions.
93 changes: 68 additions & 25 deletions scala2/src/main/scala/jurisk/adventofcode/y2023/Advent10.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import jurisk.geometry.{Coords2D, Field2D}
import jurisk.utils.CollectionOps.IterableOps
import jurisk.utils.FileInput._
import jurisk.utils.Parsing.StringOps
import cats.implicits._

import scala.collection.immutable.ArraySeq

object Advent10 {
final case class Input(
Expand Down Expand Up @@ -56,32 +59,30 @@ object Advent10 {

def parse(input: String): Input = Input.parse(input)

private def dijkstraToAllTrackNodes(data: Input) = Dijkstra
.dijkstraAll(
data.animalAt,
(c: Coords2D) => connectedNeighbours(data.field, c).map(x => (x, 1)),
)

// Find distance to all nodes we can get to while going on the track, take the maximum
def part1(data: Input): Int =
Dijkstra
.dijkstraAll(
data.animalAt,
(c: Coords2D) => connectedNeighbours(data.field, c).map(x => (x, 1)),
)
.map { case (coord @ _, (parent @ _, distance)) =>
dijkstraToAllTrackNodes(data).map {
case (coord @ _, (parent @ _, distance)) =>
distance
}
.max
}.max

def part2(data: Input): Int = {
// All the track coordinates
val trackCoords = Dijkstra
.dijkstraAll(
data.animalAt,
(c: Coords2D) => connectedNeighbours(data.field, c).map(x => (x, 1)),
)
.keySet

// The field with only track cells left, others are Empty
val onlyTrack = data.field.mapByCoordsWithValues { case (c, v) =>
if (trackCoords.contains(c)) v else Empty
}
val fromPicksShoelace = part2PicksShoelace(data)
val fromMarkRightSideOfTrack = part2MarkRightSideOfTrack(data)
assert(
fromPicksShoelace == fromMarkRightSideOfTrack,
s"Expected to get same results: $fromPicksShoelace and $fromMarkRightSideOfTrack",
)
fromPicksShoelace
}

private def findStart(onlyTrack: Field2D[Pipe]): CoordsWithDirection = {
// We don't know which direction is inside and which is outside for the animal coordinates,
// but we can figure it out for the top left coordinates. This depends on the order in which `allCoords`
// returns coordinates.
Expand All @@ -99,17 +100,59 @@ object Advent10 {
case Pipe.S_E => E
}

val start = CoordsWithDirection(
CoordsWithDirection(
coords = topLeftFullCoord,
direction = topLeftStartDirection,
)
}

// Which direction was the animal facing on each track segment?
val trackCoordsWithAnimalDirection =
Bfs.bfsReachable[CoordsWithDirection](
private def walkTrack(data: Field2D[Pipe]) = {
val start = findStart(data)

Bfs
.bfsReachable[CoordsWithDirection](
start,
x => x.nextOnTrack(data.field) :: Nil,
x => x.nextOnTrack(data) :: Nil,
)
}

def part2PicksShoelace(data: Input): Int = {
// All the track coordinates
val trackCoords = dijkstraToAllTrackNodes(data).keySet

// The field with only track cells left, others are Empty
val onlyTrack = data.field.mapByCoordsWithValues { case (c, v) =>
if (trackCoords.contains(c)) v else Empty
}

// Which direction was the animal facing on each track segment?
val trackCoordsWithAnimalDirection = walkTrack(onlyTrack)

// Track coordinates in walking order
val trackCoordsInWalkingOrder = ArraySeq.from(
trackCoordsWithAnimalDirection
.map(_.coords)
)

val boundaryPoints = trackCoordsInWalkingOrder.length

val area = Coords2D.areaOfSimplePolygon(trackCoordsInWalkingOrder)

// https://en.wikipedia.org/wiki/Pick%27s_theorem
(area - (boundaryPoints.toDouble / 2.0) + 1.0).toInt
}

def part2MarkRightSideOfTrack(data: Input): Int = {
// All the track coordinates
val trackCoords = dijkstraToAllTrackNodes(data).keySet

// The field with only track cells left, others are Empty
val onlyTrack = data.field.mapByCoordsWithValues { case (c, v) =>
if (trackCoords.contains(c)) v else Empty
}

// Which direction was the animal facing on each track segment?
val trackCoordsWithAnimalDirection = walkTrack(onlyTrack)

// Which cells were on the right of the track, as the animal was walking around it?
val rightCoordinateSeeds = trackCoordsWithAnimalDirection
Expand Down
19 changes: 19 additions & 0 deletions scala2/src/main/scala/jurisk/geometry/Coords2D.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package jurisk.geometry
import cats.implicits._
import jurisk.utils.Parsing.StringOps

import scala.collection.immutable.ArraySeq

final case class Coords2D(x: Int, y: Int) {
def +(other: Coords2D): Coords2D =
Coords2D(x + other.x, y + other.y)
Expand Down Expand Up @@ -47,6 +49,7 @@ final case class Coords2D(x: Int, y: Int) {
}

object Coords2D {

implicit val readingOrdering: Ordering[Coords2D] =
Ordering[(Int, Int)].contramap(c => (c.y, c.x))

Expand Down Expand Up @@ -89,4 +92,20 @@ object Coords2D {
} else
s"Expected $a and $b to have same x or y coordinates, but they do not".fail

// https://en.wikipedia.org/wiki/Shoelace_formula#Shoelace_formula
def areaOfSimplePolygon(seq: IndexedSeq[Coords2D]): Double = {
val n = seq.length

val determinants = (0 to n)
.map(_ % n)
.toList
.sliding2
.map { case (a, b) =>
(seq(a).x.toDouble * seq(b).y.toDouble) -
(seq(a).y.toDouble * seq(b).x.toDouble)
}

(determinants.sum / 2.0).abs
}

}

0 comments on commit 1024c9c

Please sign in to comment.