@@ -2,10 +2,7 @@ package tr.emreone.adventofcode.days
22
33import tr.emreone.kotlin_utils.Resources
44import tr.emreone.kotlin_utils.automation.Day
5- import java.lang.IllegalArgumentException
65import java.util.*
7- import kotlin.math.max
8- import kotlin.math.min
96
107class Day10 : Day (
118 10 ,
@@ -14,57 +11,82 @@ class Day10 : Day(
1411 session = Resources .resourceAsString("session.cookie")
1512) {
1613
17- class PipeMaze (input : List <List <Char >>) {
14+ class Maze (input : List <List <Char >>) {
15+ val NORTH_EAST = ' └' // oder 'L'
16+ val NORTH_WEST = ' ┘' // oder 'J'
17+ val SOUTH_EAST = ' ┌' // oder 'F'
18+ val SOUTH_WEST = ' ┐' // oder '7'
19+ val NORTH_SOUTH = ' │' // oder '|'
20+ val WEST_EAST = ' ─' // oder '-'
21+ val EMPTY_CELL_CHAR = ' ∙' // '.'
22+ val FILLED_CELL_CHAR = ' #'
23+
24+ private val PIPES_TO_NORTH = listOf (NORTH_SOUTH , NORTH_WEST , NORTH_EAST )
25+
1826 val width = input.first().size
1927 val height = input.size
2028 val grid = input.mapIndexed { y, line ->
2129 line.mapIndexed { x, p ->
22- Pipe (x, y, p)
30+ Pipe (x, y, convertChar(p))
31+ }
32+ }
33+
34+ /* *
35+ * convert the char for a better visualization
36+ */
37+ private fun convertChar (c : Char ): Char {
38+ return when (c) {
39+ ' L' -> NORTH_EAST
40+ ' J' -> NORTH_WEST
41+ ' |' -> NORTH_SOUTH
42+ ' F' -> SOUTH_EAST
43+ ' 7' -> SOUTH_WEST
44+ ' -' -> WEST_EAST
45+ ' S' -> ' S'
46+ else -> EMPTY_CELL_CHAR
2347 }
2448 }
25- }
2649
27- class Pipe (val x : Int , val y : Int , val pipe : Char ) {
2850 val allowedPipesInDirection: Map <Char , List <Char >> = mapOf (
29- ' n ' to listOf (' | ' , ' 7 ' , ' F ' , ' S' ),
30- ' e ' to listOf (' - ' , ' J ' , ' 7 ' , ' S' ),
31- ' s ' to listOf (' | ' , ' L ' , ' J ' , ' S' ),
32- ' w ' to listOf (' - ' , ' F ' , ' L ' , ' S' )
51+ ' N ' to listOf (NORTH_SOUTH , SOUTH_WEST , SOUTH_EAST , ' S' ),
52+ ' E ' to listOf (WEST_EAST , NORTH_WEST , SOUTH_WEST , ' S' ),
53+ ' S ' to listOf (NORTH_SOUTH , NORTH_WEST , NORTH_EAST , ' S' ),
54+ ' W ' to listOf (WEST_EAST , SOUTH_EAST , NORTH_EAST , ' S' )
3355 )
3456
35- private fun getNeighbours (maze : PipeMaze ): Map <Char , Pipe > {
57+ private fun getNeighbours (pipe : Pipe ): Map <Char , Pipe > {
3658 return buildMap {
37- if (this @Pipe .y > 0 ) {
38- this [' n ' ] = maze .grid[y - 1 ][x]
59+ if (pipe .y > 0 ) {
60+ this [' N ' ] = this @Maze .grid[pipe. y - 1 ][pipe. x]
3961 }
40- if (this @Pipe .x < maze .width - 1 ) {
41- this [' e ' ] = maze .grid[y][x + 1 ]
62+ if (pipe .x < this @Maze .width - 1 ) {
63+ this [' E ' ] = this @Maze .grid[pipe. y][pipe. x + 1 ]
4264 }
43- if (this @Pipe .y < maze .height - 1 ) {
44- this [' s ' ] = maze .grid[y + 1 ][x]
65+ if (pipe .y < this @Maze .height - 1 ) {
66+ this [' S ' ] = this @Maze .grid[pipe. y + 1 ][pipe. x]
4567 }
46- if (this @Pipe .x > 0 ) {
47- this [' w ' ] = maze .grid[y][x - 1 ]
68+ if (pipe .x > 0 ) {
69+ this [' W ' ] = this @Maze .grid[pipe. y][pipe. x - 1 ]
4870 }
4971 }
5072 }
5173
52- fun getValidNeighbours (maze : PipeMaze ): List <Pipe > {
53- val neighboursCompass = getNeighbours(maze ).filter {
54- it.value.pipe != ' . '
74+ fun getValidNeighbours (pipe : Pipe ): List <Pipe > {
75+ val neighboursCompass = getNeighbours(pipe ).filter {
76+ it.value.pipeChar != EMPTY_CELL_CHAR
5577 }
56- val lookAtDirection = when (this . pipe) {
57- ' S' -> listOf (' n ' , ' e ' , ' s ' , ' w ' )
58- ' | ' -> listOf (' n ' , ' s ' )
59- ' - ' -> listOf (' e ' , ' w ' )
60- ' L ' -> listOf (' n ' , ' e ' )
61- ' J ' -> listOf (' n ' , ' w ' )
62- ' 7 ' -> listOf (' s ' , ' w ' )
63- ' F ' -> listOf (' s ' , ' e ' )
64- else -> throw IllegalArgumentException ()
78+ val lookAtDirection = when (pipe.pipeChar ) {
79+ ' S' -> listOf (' N ' , ' E ' , ' S ' , ' W ' )
80+ NORTH_SOUTH -> listOf (' N ' , ' S ' )
81+ WEST_EAST -> listOf (' W ' , ' E ' )
82+ NORTH_EAST -> listOf (' N ' , ' E ' )
83+ NORTH_WEST -> listOf (' N ' , ' W ' )
84+ SOUTH_WEST -> listOf (' S ' , ' W ' )
85+ SOUTH_EAST -> listOf (' S ' , ' E ' )
86+ else -> throw IllegalArgumentException ()
6587 }
6688 return neighboursCompass.mapNotNull {
67- if (it.key in lookAtDirection && it.value.pipe in allowedPipesInDirection[it.key]!! ) {
89+ if (it.key in lookAtDirection && it.value.pipeChar in allowedPipesInDirection[it.key]!! ) {
6890 it.value
6991 }
7092 else {
@@ -73,6 +95,55 @@ class Day10 : Day(
7395 }
7496 }
7597
98+ fun getLoop (startPipe : Pipe ): List <Pipe >? {
99+ val stack = ArrayDeque <Triple <Pipe , List <Pipe >, List <Pipe >>>()
100+ stack.add(Triple (startPipe, emptyList(), emptyList()))
101+
102+ while (stack.isNotEmpty()) {
103+ val (currentPipe, visited, path) = stack.removeLast()
104+
105+ // in order to make a loop, the path should be at least 3
106+ if (currentPipe.pipeChar == ' S' && path.size >= 3 ) return path
107+ if (currentPipe in visited) continue
108+
109+ for (p in getValidNeighbours(currentPipe)) {
110+ stack.add(Triple (p, visited + currentPipe, path + currentPipe))
111+ }
112+ }
113+
114+ return null
115+ }
116+
117+ fun isCellEnclosed (x : Int , y : Int ): Boolean {
118+ if (grid[y][x].pipeChar != EMPTY_CELL_CHAR ) return false
119+
120+ val crossingPipes = if (x < width / 2 ) {
121+ grid[y].subList(0 , x).count {
122+ it.pipeChar in PIPES_TO_NORTH
123+ }
124+ }
125+ else {
126+ grid[y].subList(x, this .width).count {
127+ it.pipeChar in PIPES_TO_NORTH
128+ }
129+ }
130+
131+ if (crossingPipes.mod(2 ) == 1 ) {
132+ this .grid[y][x].pipeChar = FILLED_CELL_CHAR
133+ return true
134+ }
135+ return false
136+ }
137+
138+ fun print () {
139+ this .grid.forEach { line ->
140+ println (line.joinToString { it.pipeChar.toString() })
141+ }
142+ println ()
143+ }
144+ }
145+
146+ class Pipe (val x : Int , val y : Int , var pipeChar : Char ) {
76147 override fun equals (other : Any? ): Boolean {
77148 if (this == = other) return true
78149 if (javaClass != other?.javaClass) return false
@@ -81,89 +152,57 @@ class Day10 : Day(
81152
82153 if (x != other.x) return false
83154 if (y != other.y) return false
84- if (pipe != other.pipe ) return false
155+ if (pipeChar != other.pipeChar ) return false
85156
86157 return true
87158 }
88159
89160 override fun hashCode (): Int {
90161 var result = x
91162 result = 31 * result + y
92- result = 31 * result + pipe .hashCode()
163+ result = 31 * result + pipeChar .hashCode()
93164 return result
94165 }
95166
96167 override fun toString (): String {
97- return " Pipe ($x |$y ) with $pipe "
168+ return " Pipe ' $pipeChar ' ($x |$y ) "
98169 }
99170 }
100171
101- fun loopFinder (maze : PipeMaze , prevPipe : Pipe ? , currentPipe : Pipe , visited : List <Pipe >, path : List <Pipe >): List <Pipe >? {
102- val neighbours = currentPipe.getValidNeighbours(maze)
103-
104- val unVisitedNeighbours = neighbours.filter { it !in visited && it != prevPipe }
105- if (unVisitedNeighbours.isEmpty()) return null
106-
107- val newVisited = visited.toMutableList()
108- if (currentPipe.pipe != ' S' ) {
109- newVisited.add(currentPipe)
110- }
172+ override fun part1 (): Int {
173+ val maze = Maze (inputAsGrid)
111174
112- return unVisitedNeighbours.firstNotNullOfOrNull {
113- if (it.pipe == ' S' && path.size > 3 ) {
114- return @firstNotNullOfOrNull path + currentPipe
115- }
116- return @firstNotNullOfOrNull loopFinder(maze, currentPipe, it, newVisited, path + currentPipe)
175+ val start = maze.grid.flatten().first {
176+ it.pipeChar == ' S'
117177 }
118- }
119-
120- fun loopFinder (maze : PipeMaze , startPipe : Pipe ): List <Pipe >? {
121- val queue = LinkedList <Pair <Pipe , List <Pipe >>>()
122- val visited = mutableSetOf<Pipe >()
123- queue.add(Pair (startPipe, listOf (startPipe)))
124-
125- while (queue.isNotEmpty()) {
126- val (currentPipe, path) = queue.poll()
127-
128- if (currentPipe.pipe == ' S' && path.size > 3 ) {
129- return path
130- }
131-
132- if (currentPipe.pipe == ' S' ) {
133- visited.add(currentPipe)
134- }
135178
136- val neighbours = currentPipe.getValidNeighbours(maze)
179+ val path = maze.getLoop(start) ? : return - 1
137180
138- for (neighbour in neighbours) {
139- if (neighbour !in visited) {
140- val newPath = path + neighbour
141- queue.add(Pair (neighbour, newPath))
142- }
143- }
144- }
145-
146- return null
181+ return path.size / 2
147182 }
148183
149- override fun part1 (): Int {
150- val maze = PipeMaze (inputAsGrid)
184+ override fun part2 (): Long {
185+ val maze = Maze (inputAsGrid)
151186
152187 val start = maze.grid.flatten().first {
153- it.pipe == ' S'
188+ it.pipeChar == ' S'
154189 }
190+ val path = maze.getLoop(start) ? : return - 1
155191
156- val visited = emptyList< Pipe >()
157- val path = loopFinder( maze, start)
158-
159- path?.forEach {
160- println (it)
192+ // remove unconnected pipe parts
193+ maze.grid.flatten().forEach {
194+ if ( ! path.contains(it)) {
195+ maze.grid[it.y][it.x].pipeChar = maze. EMPTY_CELL_CHAR
196+ }
161197 }
162- return (path?.size ? : 0 ) / 2
163- }
164198
165- override fun part2 (): Long {
166- return 0L
199+ val enclosed = maze.grid.flatten().count {
200+ maze.isCellEnclosed(it.x, it.y)
201+ }.toLong()
202+
203+ maze.print ()
204+
205+ return enclosed
167206 }
168207
169- }
208+ }
0 commit comments