Skip to content

Commit d7d359d

Browse files
committed
finish day10
1 parent 252e4ad commit d7d359d

File tree

14 files changed

+515
-93
lines changed

14 files changed

+515
-93
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ venv
99
session.cookie
1010

1111
src/**/*.txt
12+
!src/test/**/*.txt
1213

1314
*.iml
1415

src/main/kotlin/tr/emreone/adventofcode/days/Day10.kt

Lines changed: 130 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ package tr.emreone.adventofcode.days
22

33
import tr.emreone.kotlin_utils.Resources
44
import tr.emreone.kotlin_utils.automation.Day
5-
import java.lang.IllegalArgumentException
65
import java.util.*
7-
import kotlin.math.max
8-
import kotlin.math.min
96

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

Comments
 (0)