Skip to content

Commit

Permalink
Extract DFS to library
Browse files Browse the repository at this point in the history
  • Loading branch information
sim642 committed Dec 10, 2023
1 parent b9ec10d commit 6a09be1
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 38 deletions.
45 changes: 8 additions & 37 deletions src/main/scala/eu/sim642/adventofcode2023/Day10.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package eu.sim642.adventofcode2023

import eu.sim642.adventofcodelib.{Geometry, Grid}
import eu.sim642.adventofcodelib.GridImplicits.*
import eu.sim642.adventofcodelib.IteratorImplicits.*
import eu.sim642.adventofcodelib.graph.{BFS, Distances, GraphTraversal, UnitNeighbors}
import eu.sim642.adventofcodelib.graph.*
import eu.sim642.adventofcodelib.pos.Pos

import scala.annotation.tailrec
import eu.sim642.adventofcodelib.{Geometry, Grid}

object Day10 {

Expand All @@ -20,8 +17,8 @@ object Day10 {
'S' -> Pos.axisOffsets.toSet,
)

def findLoop(grid: Grid[Char]): Distances[Pos] = {
val graphTraversal = new GraphTraversal[Pos] with UnitNeighbors[Pos] {
def loopTraversal(grid: Grid[Char]): GraphTraversal[Pos] with UnitNeighbors[Pos] = {
new GraphTraversal[Pos] with UnitNeighbors[Pos] {
override val startNode: Pos = grid.posOf('S')

override def unitNeighbors(pos: Pos): IterableOnce[Pos] = {
Expand All @@ -34,11 +31,10 @@ object Day10 {
} yield newPos
}
}

BFS.traverse(graphTraversal)
}

def farthestDistance(grid: Grid[Char]): Int = findLoop(grid).distances.values.max
def farthestDistance(grid: Grid[Char]): Int =
BFS.traverse(loopTraversal(grid)).distances.values.max

trait Part2Solution {
def enclosedTiles(grid: Grid[Char]): Int
Expand All @@ -50,7 +46,7 @@ object Day10 {
*/
object RayCastingPart2Solution extends Part2Solution {
override def enclosedTiles(grid: Grid[Char]): Int = {
val loop = findLoop(grid).nodes
val loop = BFS.traverse(loopTraversal(grid)).nodes

// grid with only loop: unconnected pipes removed and start pipe determined
val loopGrid = {
Expand Down Expand Up @@ -101,32 +97,7 @@ object Day10 {
*/
object PicksTheoremPart2Solution extends Part2Solution {
override def enclosedTiles(grid: Grid[Char]): Int = {

// TODO: generalize DFS
@tailrec
def dfs(pos: Pos, visited: Set[Pos], loop: List[Pos]): List[Pos] = {
if (visited.contains(pos))
loop
else {
val newVisited = visited + pos
val newPoss = for {
offset <- pipeDirections(grid(pos))
newPos = pos + offset
if grid.containsPos(newPos)
if grid(newPos) != '.'
if pipeDirections(grid(newPos)).contains(-offset)
if !newVisited.contains(newPos)
} yield newPos

if (newPoss.isEmpty)
pos :: loop
else
dfs(newPoss.head, newVisited, pos :: loop)
}
}

val loop = dfs(grid.posOf('S'), Set.empty, Nil)

val loop = DFS.traverse(loopTraversal(grid)).nodeOrder
Geometry.polygonArea(loop) - loop.size / 2 + 1
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/eu/sim642/adventofcodelib/Geometry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ object Geometry {
* Calculates the area of a simple polygon using the shoelace formula.
* @see [[https://en.wikipedia.org/wiki/Shoelace_formula]]
*/
def polygonArea(poss: Seq[Pos]): Int = {
def polygonArea(poss: collection.Seq[Pos]): Int = {
((poss.last +: poss).iterator
.zipWithTail
.map(_ cross _)
Expand Down
42 changes: 42 additions & 0 deletions src/main/scala/eu/sim642/adventofcodelib/graph/DFS.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package eu.sim642.adventofcodelib.graph

import scala.collection.mutable

object DFS {
// TODO: reduce duplication without impacting performance

// copied from BFS
def traverse[A](graphTraversal: GraphTraversal[A] with UnitNeighbors[A]): Distances[A] with Order[A] = {
val visitedDistance: mutable.Map[A, Int] = mutable.Map.empty
val visitedOrder: mutable.Buffer[A] = mutable.Buffer.empty
val toVisit: mutable.Stack[(Int, A)] = mutable.Stack.empty

def enqueue(node: A, dist: Int): Unit = {
toVisit.push((dist, node))
}

enqueue(graphTraversal.startNode, 0)

while (toVisit.nonEmpty) {
val (dist, node) = toVisit.pop()
if (!visitedDistance.contains(node)) {
visitedDistance(node) = dist
visitedOrder += node

def goNeighbor(newNode: A): Unit = {
// cannot avoid unnecessary stack duplication here: https://11011110.github.io/blog/2013/12/17/stack-based-graph-traversal.html
val newDist = dist + 1
enqueue(newNode, newDist)
}

graphTraversal.unitNeighbors(node).iterator.foreach(goNeighbor)
}
}

new Distances[A] with Order[A] {
override def distances: collection.Map[A, Int] = visitedDistance

override def nodeOrder: collection.Seq[A] = visitedOrder
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ trait Distances[A] {

def nodes: collection.Set[A] = distances.keySet
}

trait Order[A] {
def nodeOrder: collection.Seq[A]
}

0 comments on commit 6a09be1

Please sign in to comment.