Skip to content

Commit

Permalink
Day 10 part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
kohloderso committed Dec 27, 2023
1 parent 7a63b99 commit db230f3
Showing 1 changed file with 57 additions and 125 deletions.
182 changes: 57 additions & 125 deletions src/solutions23/Day10.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package solutions23

class PipeGrid(val grid: Array<CharArray>, val startRow: Int, val startCol: Int) {
class PipeGrid(val grid: Array<CharArray>, private val startRow: Int, private val startCol: Int) {

val markedPos = Array(grid.size) { BooleanArray(grid[0].size) { false }}
private val markedPos = Array(grid.size) { BooleanArray(grid[0].size) { false }}

companion object {
fun parse(input: List<String>): PipeGrid {
Expand All @@ -19,6 +19,24 @@ class PipeGrid(val grid: Array<CharArray>, val startRow: Int, val startCol: Int)
}
return Pair(-1,-1)
}

// 'S' should be treated like 7
fun pipeRight(c: Char): Boolean = when (c) {
'F', '-', 'L' -> true
else -> false
}
fun pipeLeft(c: Char): Boolean = when (c) {
'J', '-', '7' -> true
else -> false
}
fun pipeDown(c: Char): Boolean = when (c) {
'|', 'F', '7', 'S' -> true
else -> false
}
fun pipeUp(c: Char): Boolean = when (c) {
'J', '|', 'L' -> true
else -> false
}
}

// Length of the cycle is the only thing that counts for part 1
Expand Down Expand Up @@ -59,151 +77,65 @@ class PipeGrid(val grid: Array<CharArray>, val startRow: Int, val startCol: Int)
return counter
}

// 'S' should be treated like 7
fun pipeRight(row: Int, col: Int): Boolean = when (grid[row][col]) {
'F', '-', 'L' -> true
else -> false
}
fun pipeLeft(row: Int, col: Int): Boolean = when (grid[row][col]) {
'J', '-', '7', 'S' -> true
else -> false
}
fun pipeDown(row: Int, col: Int): Boolean = when (grid[row][col]) {
'|', 'F', '7', 'S' -> true
else -> false
}
fun pipeUp(row: Int, col: Int): Boolean = when (grid[row][col]) {
'J', '|', 'L' -> true
else -> false
}

fun connectedPipes(row1: Int, col1: Int, row2: Int, col2: Int): Boolean {
val vDir = row2 - row1
val hDir = col2 - col1
if(hDir == 1 && vDir == 0 && pipeRight(row1, col1) && pipeLeft(row2, col2)) return true
if(hDir == -1 && vDir == 0 && pipeLeft(row1, col1) && pipeRight(row2, col2)) return true
if(hDir == 0 && vDir == 1 && pipeDown(row1, col1) && pipeUp(row2, col2)) return true
if(hDir == 0 && vDir == -1 && pipeUp(row1, col1) && pipeDown(row2, col2)) return true
return false
}

/**
* Follow parallel pipe tracks that belong to the cycle in the original grid.
* Return the position of a tile that does not belong to the cycle but can be reached by the parallel tracks.
* Return null if no such tile exists.
*/
fun followParallelTracks(row1: Int, col1: Int, row2: Int, col2: Int, vDir: Int, hDir: Int): Pair<Int,Int>? {
var newRow1 = row1
var newCol1 = col1
var newRow2 = row2
var newCol2 = col2
if(newRow1 < 0 || newRow1 >= grid.size || newCol1 < 0 || newCol1 >= grid[0].size
|| newRow2 < 0 || newRow2 >= grid.size || newCol2 < 0 || col2 >= grid[0].size
|| !markedPos[row1][col1] || !markedPos[row2][col2])
return null
// if both fields belong to the cycle but are not directly connected, then it's a parallel track
while(markedPos[newRow1][newCol1] && markedPos[newRow2][newCol2]
&& !connectedPipes(newRow1, newCol1, newRow2, newCol2)) {
newRow1 += vDir
newCol1 += hDir
newRow2 += vDir
newCol2 += hDir
if(newRow1 < 0 || newRow1 >= grid.size || newCol1 < 0 || newCol1 >= grid[0].size
|| newRow2 < 0 || newRow2 >= grid.size || newCol2 < 0 || col2 >= grid[0].size)
return null
}
if(!markedPos[newRow1][newCol1]) return Pair(newRow1, newCol1)
if(!markedPos[newRow2][newCol2]) return Pair(newRow2, newCol2)
return null
}

/**
* Insert extra fields between each row and column and around the grid.
* Only copy the pipe parts that belong to the relevant cycle.
*/
fun padGrid(): Array<Array<Char>> =
Array(grid.size*2+1) { i ->
fun padGrid(sLetter: Char): Array<Array<Char>> {
val paddedGrid = Array(grid.size*2+1) { i ->
Array(grid[0].size*2+1) { j ->
if(i % 2 == 1 && j % 2 == 1) {
grid[i/2][j/2]
} else {
// check if there is horizontal connection between two adjacent fields
if(i/2-1 >= 0 && i/2+1 < grid[0].size && pipeRight(i/2-1,j/2) && pipeLeft(i/2+1,j/2)) {
'-'
// check if there is a vertical connection between two adjacent fields
} else if(j/2-1 >= 0 && j/2+1 < grid[0].size && pipeDown(i/2,j/2-1) && pipeUp(i/2,j/2+1)) {
'|'
} else {
'.'
}
if(i % 2 == 1 && j % 2 == 1 && markedPos[i/2][j/2]) {
if(grid[i/2][j/2] == 'S') sLetter
else grid[i/2][j/2]
} else '.'
}
}
for(i in paddedGrid.indices) {
for(j in paddedGrid[0].indices) {
// check if there is a vertical connection between two adjacent fields
if(i % 2 == 0 && i-1 >= 0 && i+1 < paddedGrid.size && pipeDown(paddedGrid[i-1][j]) && pipeUp(paddedGrid[i+1][j])) {
paddedGrid[i][j] = '|'
}
// check if there is horizontal connection between two adjacent fields
if(j % 2 == 0 && j-1 >= 0 && j+1 < paddedGrid[0].size && pipeRight(paddedGrid[i][j-1]) && pipeLeft(paddedGrid[i][j+1])) {
paddedGrid[i][j] = '-'
}
}
}
return paddedGrid
}

fun floodFillCount(): Int {
val paddedGrid = padGrid()

fun floodFillCount(sLetter: Char): Int {
val paddedGrid = padGrid(sLetter)
var count = 0
val queue = ArrayDeque<Pair<Int,Int>>()
queue.add(Pair(startRow, startCol))
queue.add(Pair(0, 0))
while(queue.isNotEmpty()) {
val (row, col) = queue.removeFirst()
// these are the areas where we're not allowed to go
if(row < 0 || row >= paddedGrid.size || col < 0 || col >= paddedGrid[0].size) continue
// if we are inside the normal grid, check whether it's a marked position belonging to the cycle
if(row > 0 && row <= grid.size && col > 0 && col <= grid[0].size && markedPos[row-1][col-1]) continue
// these are the tiles we already visited
if(paddedGrid[row][col] == 'O') continue
// check whether it's a free and unvisited field
if(paddedGrid[row][col] != '.') continue
// mark all visited tiles
paddedGrid[row][col] = 'O'
// if we are inside the normal grid, increment the count
if(row > 0 && row <= grid.size && col > 0 && col <= grid[0].size) count++
if(row % 2 == 1 && col % 2 == 1) count++
queue.add(Pair(row+1, col))
queue.add(Pair(row-1, col))
queue.add(Pair(row, col+1))
queue.add(Pair(row, col-1))
// check the parallel track situation, don't forget to take padding into account
val parallelTracksPos = mutableListOf<Pair<Int,Int>?>()
parallelTracksPos.add(followParallelTracks(row, col-1, row, col-2, 1, 0 ))
parallelTracksPos.add(followParallelTracks(row, col-1, row, col, 1, 0 ))
parallelTracksPos.add(followParallelTracks(row-2, col-1, row-2, col-2, -1, 0 ))
parallelTracksPos.add(followParallelTracks(row-2, col-1, row-2, col, -1, 0 ))
parallelTracksPos.add(followParallelTracks(row-1, col-2, row-2, col-2, 0, -1 ))
parallelTracksPos.add(followParallelTracks(row-1, col-2, row, col-2, 0, -1 ))
parallelTracksPos.add(followParallelTracks(row-1, col, row-2, col, 0, 1 ))
parallelTracksPos.add(followParallelTracks(row-1, col, row, col, 0, 1 ))
queue.addAll(parallelTracksPos.filterNotNull().map { it.first + 1 to it.second + 1 })
}
// print stuff for debugging
for(i in paddedGrid.indices) {
for(j in paddedGrid[0].indices) {
if(paddedGrid[i][j] == 'O') print('O')
else if(i > 0 && i <= grid.size && j > 0 && j <= grid[0].size && markedPos[i-1][j-1])
print(grid[i-1][j-1])
else print('*')
}
println()
}
// sanity check: count I's in insideGrid
val insideGrid = Array(grid.size) { CharArray(grid[0].size) { '.' } }
for(i in grid.indices) {
for(j in grid[0].indices) {
if(paddedGrid[i+1][j+1] != 'O' && !markedPos[i][j]) insideGrid[i][j] = 'I'
}
}
var iCount = 0
for(i in insideGrid.indices) {
for(j in insideGrid[0].indices) {
if(insideGrid[i][j] == 'I') iCount++
}
}
println("number of I's in insideGrid: $iCount")
return count
}

fun computeInsideTiles(): Int {
fun computeInsideTiles(sLetter: Char): Int {
val cycleLength = findCycleLengthAndMarkCycle()
// start at upper left corner of padded grid
val outsideTiles = floodFillCount()
// everything that's not marked 'O' and does not belong to the cycle itself is inside
val outsideTiles = floodFillCount(sLetter)
// everything that's not outside and does not belong to the cycle itself is inside
return grid.size * grid[0].size - outsideTiles - cycleLength
}
}
Expand All @@ -215,21 +147,21 @@ fun main() {
return grid.findCycleLengthAndMarkCycle()/2
}

fun part2(input: List<String>): Int {
fun part2(input: List<String>, sLetter: Char): Int {
val grid = PipeGrid.parse(input)
return grid.computeInsideTiles()
return grid.computeInsideTiles(sLetter)
}

val testInput = parseLines("Day10_test")
check(part1(testInput) == 8)
val test2 = part2(parseLines("Day10_test2"))
val test2 = part2(parseLines("Day10_test2"), 'F')
check(test2 == 8)
val test3 = part2(parseLines("Day10_test3"))
val test3 = part2(parseLines("Day10_test3"), '7')
check(test3 == 10)
// val testInput4 = parseLines("Day10_test4")
// check(part2(testInput4) == 4) // works when S is treated like F
val testInput4 = parseLines("Day10_test4")
check(part2(testInput4, 'F') == 4)

val input = parseLines("Day10")
println(part1(input))
println(part2(input))
println(part2(input, '7'))
}

0 comments on commit db230f3

Please sign in to comment.