|
| 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