Skip to content

Commit 4c364b4

Browse files
committed
Solutions for both parts of Day 10 problem
1 parent 839e1c3 commit 4c364b4

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed

day10/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Execute this code via `scala day5.scala <filename>`.
2+
3+
### Observations
4+
5+
1. [Scaladoc](https://docs.scala-lang.org/) [Scala API docs](https://www.scala-lang.org/api/3.3.1/) are really easy to use.
6+
2. VS Code using [Metals](https://scalameta.org/metals/) LSP failed to recognize
7+
files via their soft links.
8+
3. "Format document" command did not always work with Metals LSP.

day10/day10.scala

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import io.Source
2+
import scala.util.Using
3+
import scala.collection.mutable.HashMap
4+
import scala.collection.mutable.HashSet
5+
import scala.collection.mutable.Stack
6+
import scala.collection.mutable.ListBuffer
7+
import java.util.Collection
8+
9+
type Node = (Int, Int)
10+
type Graph = Map[Node, Char]
11+
case class Sketch(graph: Graph, num_rows: Int, num_cols: Int)
12+
13+
def createSketch(filename: String): Sketch = {
14+
val lines = Using(Source.fromFile(filename)) {
15+
_.getLines.toList
16+
}.get.toList
17+
val graph = lines
18+
.map(_.toCharArray())
19+
.zipWithIndex
20+
.foldLeft(Map[Node, Char]()) { (acc, row) =>
21+
val (chars, row_id) = row
22+
chars.zipWithIndex.filter { _._1 != '.' }.foldLeft(acc) { (acc, col) =>
23+
val (char, col_id) = col
24+
acc + ((row_id, col_id) -> char)
25+
}
26+
}
27+
Sketch(graph, lines.size, lines.head.length)
28+
}
29+
30+
def getPipeAtStartNode(node: Node, graph: Graph): Char = {
31+
def crosser(a: String, b: String) = for (i <- a.toCharArray; j <- b.toCharArray) yield (i, j)
32+
33+
val top_tile = graph.getOrElse((node._1 - 1, node._2), '.')
34+
val bottom_tile = graph.getOrElse((node._1 + 1, node._2), '.')
35+
val left_tile = graph.getOrElse((node._1, node._2 - 1), '.')
36+
val right_tile = graph.getOrElse((node._1, node._2 + 1), '.')
37+
if (crosser("|7F", "|JL").contains((top_tile, bottom_tile))) then return '|'
38+
if (crosser("|7F", "-FL").contains((top_tile, left_tile))) then return 'J'
39+
if (crosser("|7F", "-7J").contains((top_tile, right_tile))) then return 'L'
40+
if (crosser("-LF", "|LJ").contains((left_tile, bottom_tile))) then return '7'
41+
if (crosser("-LF", "-J7").contains((left_tile, right_tile))) then return '-'
42+
if (crosser("|LJ", "-7J").contains((bottom_tile, right_tile))) then return 'F'
43+
throw IllegalStateException()
44+
}
45+
46+
def getLoop(startNode: Node, graph:Graph): List[Node] = {
47+
def getNeighbors(node: Node): List[Node] = {
48+
val pipe = if graph(node) == 'S' then getPipeAtStartNode(startNode, graph) else graph(node)
49+
val possibleNeighbors = pipe match {
50+
case '|' => List((node._1 - 1, node._2), (node._1 + 1, node._2))
51+
case '-' => List((node._1, node._2 - 1), (node._1, node._2 + 1))
52+
case 'L' => List((node._1 - 1, node._2), (node._1, node._2 + 1))
53+
case 'J' => List((node._1 - 1, node._2), (node._1, node._2 - 1))
54+
case '7' => List((node._1 + 1, node._2), (node._1, node._2 - 1))
55+
case 'F' => List((node._1 + 1, node._2), (node._1, node._2 + 1))
56+
}
57+
val validNeighbors = possibleNeighbors.filter(graph.contains)
58+
List.from(validNeighbors)
59+
}
60+
61+
val path = ListBuffer[Node]()
62+
path.addOne(startNode)
63+
var prevNode = startNode
64+
var node = getNeighbors(startNode).head
65+
66+
while (node != startNode) {
67+
val neighbor = getNeighbors(node).filter{ _ != prevNode }.head
68+
prevNode = node
69+
path.addOne(node)
70+
node = neighbor
71+
}
72+
path.addOne(node)
73+
path.toList
74+
}
75+
76+
def process1(sketch: Sketch) = {
77+
val startNode = sketch.graph.filter { (node, char) => char == 'S' }.head._1
78+
println(getLoop(startNode, sketch.graph).size / 2)
79+
}
80+
81+
def process2(sketch: Sketch) = {
82+
def isValidNode(node: Node): Boolean =
83+
0 <= node._1 && node._1 < sketch.num_rows && 0 <= node._2 && node._2 < sketch.num_cols
84+
85+
def getNeighbors(node: Node): List[Node] =
86+
List((node._1 - 1, node._2), (node._1 + 1, node._2), (node._1, node._2 - 1), (node._1, node._2 + 1))
87+
88+
def getContiguouslyOpenNeighboringPositions(node: Node, pipe: Char): (List[Node], List[Node]) = {
89+
val (r, c) = node
90+
pipe match {
91+
case '|' => (List((r-1, c-1), (r, c-1), (r+1, c-1)), List((r-1, c+1), (r, c+1), (r+1, c+1)))
92+
case '-' => (List((r-1, c-1), (r-1, c), (r-1, c+1)), List((r+1, c-1), (r+1, c), (r+1, c+1)))
93+
case 'L' => (List((r-1, c+1)), List((r-1, c-1), (r, c-1), (r+1, c-1), (r+1, c), (r+1, c+1)))
94+
case 'J' => (List((r-1, c-1)), List((r+1, c-1), (r+1, c), (r+1, c+1), (r, c+1), (r-1, c+1)))
95+
case '7' => (List((r+1, c-1)), List((r+1, c+1), (r, c+1), (r-1, c+1), (r-1, c), (r-1, c-1)))
96+
case 'F' => (List((r+1, c+1)), List((r-1, c+1), (r-1, c), (r-1, c-1), (r, c-1), (r+1, c-1)))
97+
}
98+
}
99+
100+
def gatherNodesLiningOneSideOfLoop(loop: List[Node]): Set[Node] = {
101+
val ret = HashSet[Node]()
102+
var prevGroup = List.empty[Node]
103+
var prevPipe = ' '
104+
for node <- loop do
105+
val pipe = if sketch.graph(node) == 'S' then getPipeAtStartNode(node, sketch.graph) else sketch.graph(node)
106+
val group = (prevPipe, pipe) match {
107+
case ('F', '7') | ('L', 'J') if prevGroup.size == 1 => { val (r, c) = prevGroup.head ; List((r, c-1)) }
108+
case ('7', 'F') | ('J', 'L') if prevGroup.size == 1 => { val (r, c) = prevGroup.head ; List((r, c+1)) }
109+
case ('7', 'J') | ('F', 'L') if prevGroup.size == 1 => { val (r, c) = prevGroup.head ; List((r-1, c)) }
110+
case ('J', '7') | ('L', 'F') if prevGroup.size == 1 => { val (r, c) = prevGroup.head ; List((r+1, c)) }
111+
case _ => {
112+
val (group1, group2)= getContiguouslyOpenNeighboringPositions(node, pipe)
113+
if (group1.exists(prevGroup.contains)) then group1 else group2
114+
}
115+
}
116+
ret.addAll(group)
117+
prevGroup = group
118+
prevPipe = pipe
119+
ret.toSet
120+
}
121+
122+
def expandGroup(group: Set[Node], nodesInLoop: Set[Node]): Set[Node] = {
123+
val expandedGroup = HashSet[Node]()
124+
val workList = HashSet.from(group)
125+
while (!workList.isEmpty) {
126+
val node = workList.head
127+
workList -= node
128+
expandedGroup.addOne(node)
129+
if (isValidNode(node)) {
130+
val neighbors = getNeighbors(node).filter { n => !nodesInLoop.contains(n) && !expandedGroup.contains(n) }
131+
workList.addAll(neighbors)
132+
}
133+
}
134+
expandedGroup.toSet
135+
}
136+
137+
def printNodes(nodes: List[Node]) = {
138+
for r <- 0 until sketch.num_rows; c <- 0 until sketch.num_cols do
139+
if (c == 0) then println("")
140+
if (nodes.contains((r,c))) then print(sketch.graph.getOrElse((r,c), "X")) else print(".")
141+
println("")
142+
}
143+
144+
val startNode = sketch.graph.filter { (node, char) => char == 'S' }.head._1
145+
val loop = getLoop(startNode, sketch.graph)
146+
val nodesInLoop = Set.from(loop)
147+
val liningNodes = gatherNodesLiningOneSideOfLoop(loop).filter { !nodesInLoop.contains(_) }
148+
val expandedGroup = expandGroup(liningNodes, nodesInLoop)
149+
val validExpandedGroup = expandedGroup.filter(isValidNode)
150+
if (expandedGroup.exists { n => !isValidNode(n) } || liningNodes.size > nodesInLoop.size) {
151+
println(sketch.num_rows * sketch.num_cols - nodesInLoop.size - validExpandedGroup.size)
152+
} else {
153+
println(validExpandedGroup.size)
154+
}
155+
}
156+
157+
@main def main(filename: String) = {
158+
val sketch = createSketch(filename)
159+
process1(sketch)
160+
process2(sketch)
161+
}

0 commit comments

Comments
 (0)