From dab6f914daebd775f590d0132cf30f90bc022a2b Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 30 Jul 2024 12:52:48 -0400 Subject: [PATCH] day 10 part 2 --- 2023/day10/main.go | 199 +++++++++++++++++++++++++++++++--------- 2023/day10/main_test.go | 12 +-- 2 files changed, 161 insertions(+), 50 deletions(-) diff --git a/2023/day10/main.go b/2023/day10/main.go index 3388928..5dafc85 100644 --- a/2023/day10/main.go +++ b/2023/day10/main.go @@ -71,46 +71,30 @@ func pipeMaze(input string, part int) int { } } - // need to find which directions are connected to the initial cell - // could traverse along the four directions and see which two lead back to - // the start... all pipes only connect two coordinates so it's actually - // fairly easy to traverse without creating a huge search space + fillGridLocation(grid, r, c) - // note this soultion was overkill for part 1 - // inputs are nice and exactly two adjacent cells that connect to S - var loopCoords map[[2]int]bool - for pipeType := range pipes { - // assign the start square to a random pipeType, then see if it will loop - grid[r][c] = pipeType - - seen := map[[2]int]bool{} - toAnalyze := [][2]int{ - {r, c}, - } - didLoop := false - for len(toAnalyze) > 0 { - coords := toAnalyze[0] - if seen[coords] { - didLoop = true - break - } - seen[coords] = true - toAnalyze = toAnalyze[1:] - - if diffs, ok := pipes[grid[coords[0]][coords[1]]]; ok { - for _, diff := range diffs { - nextRow, nextCol := coords[0]+diff[0], coords[1]+diff[1] - if isInRange(grid, nextRow, nextCol) && !seen[[2]int{nextRow, nextCol}] { - toAnalyze = append(toAnalyze, [2]int{nextRow, nextCol}) - } - } - } - - } - if didLoop { - loopCoords = seen + // traverse entire loop to determine length + loopCoords := map[[2]int]bool{} + toVisit := [][2]int{ + {r, c}, + } + for len(toVisit) > 0 { + coords := toVisit[0] + if loopCoords[coords] { break } + loopCoords[coords] = true + toVisit = toVisit[1:] + + // assumes loop is well formed, will cause a panic if not + diffs := pipes[grid[coords[0]][coords[1]]] + for _, diff := range diffs { + nextRow, nextCol := coords[0]+diff[0], coords[1]+diff[1] + if isInRange(grid, nextRow, nextCol) && !loopCoords[[2]int{nextRow, nextCol}] { + toVisit = append(toVisit, [2]int{nextRow, nextCol}) + } + } + } if part == 1 { @@ -119,26 +103,153 @@ func pipeMaze(input string, part int) int { // part 2 - newGrid := make([][]string, len(grid)) + // create copy of grid with all non-loop spots replaced with a period + reducedGrid := make([][]string, len(grid)) for i := 0; i < len(grid); i++ { for j := 0; j < len(grid[0]); j++ { if loopCoords[[2]int{i, j}] { - newGrid[i] = append(newGrid[i], grid[i][j]) + reducedGrid[i] = append(reducedGrid[i], grid[i][j]) } else { - newGrid[i] = append(newGrid[i], ".") + reducedGrid[i] = append(reducedGrid[i], ".") } } } - for _, r := range newGrid { - fmt.Println(r) + // expand grid to double plus 2 in both dimensions to account for squeezing between pipes + // the plus two is to add an empty row/column on each side for easier traversing from the outside + expandedGrid := [][]string{} + expandedGrid = append(expandedGrid, make([]string, len(reducedGrid[0])*2+2)) + + for r, rows := range reducedGrid { + expandedGrid = append(expandedGrid, make([]string, len(reducedGrid[0])*2+2)) + for c, val := range rows { + expandedGrid[r*2+1][c*2+1] = val + } + // empty row + expandedGrid = append(expandedGrid, make([]string, len(reducedGrid[0])*2+2)) } - return -1 + // fill gaps between loop coords so we have an encased area again + // we can naively try to fill in every empty spot because only ones with two valid connecting + // pipes will be filled + for r, rows := range expandedGrid { + for c, val := range rows { + if val == "" { + fillGridLocation(expandedGrid, r, c) + } + } + } + + // replacing empty strings with spaces makes the printout human readable + for r, rows := range expandedGrid { + for c, val := range rows { + if val == "" { + expandedGrid[r][c] = " " + } + } + } + + toVisit = [][2]int{ + {0, 0}, + } + seen := map[[2]int]bool{} + for len(toVisit) > 0 { + coords := toVisit[0] + toVisit = toVisit[1:] + if seen[coords] { + continue + } + seen[coords] = true + + // delete reachable dots + if expandedGrid[coords[0]][coords[1]] == "." { + expandedGrid[coords[0]][coords[1]] = " " + } + + for _, diff := range [][2]int{ + {-1, 0}, + {1, 0}, + {0, -1}, + {0, 1}, + } { + nextRow := coords[0] + diff[0] + nextCol := coords[1] + diff[1] + if isInRange(expandedGrid, nextRow, nextCol) { + if expandedGrid[nextRow][nextCol] == "." || expandedGrid[nextRow][nextCol] == " " { + toVisit = append(toVisit, [2]int{nextRow, nextCol}) + } + } + } + } + + // count remaining dots + var ans int + for _, rows := range expandedGrid { + for _, val := range rows { + if val == "." { + ans++ + } + } + } + + // for _, rows := range expandedGrid { + // fmt.Println(rows) + // } + + return ans +} + +func fillGridLocation(grid [][]string, r, c int) { + // inputs are nice and exactly two adjacent cells that connect to S + // check four directions from start + leftCol := c - 1 + rightCol := c + 1 + upRow := r - 1 + downRow := r + 1 + + var combinedString string + // check left for inRange and possible valid pipe types + if isInRange(grid, r, leftCol) && + (grid[r][leftCol] == "-" || grid[r][leftCol] == "L" || grid[r][leftCol] == "F") { + combinedString += "left" + } + // right + if isInRange(grid, r, rightCol) && + (grid[r][rightCol] == "-" || grid[r][rightCol] == "J" || grid[r][rightCol] == "7") { + combinedString += "right" + } + // up + if isInRange(grid, upRow, c) && + (grid[upRow][c] == "|" || grid[upRow][c] == "7" || grid[upRow][c] == "F") { + combinedString += "up" + } + if isInRange(grid, downRow, c) && + (grid[downRow][c] == "|" || grid[downRow][c] == "J" || grid[downRow][c] == "L") { + combinedString += "down" + } + + switch combinedString { + case "leftup": + grid[r][c] = "J" + case "leftdown": + grid[r][c] = "7" + case "rightup": + grid[r][c] = "L" + case "rightdown": + grid[r][c] = "F" + case "leftright": + grid[r][c] = "-" + case "updown": + grid[r][c] = "|" + // default: + // do not panic so we can use this function more naively for the expanded grid + // could return an error instead and choose to check it for part1 where we NEED it to find a result + // panic("ineligible configuration: " + combinedString) + } } func isInRange(grid [][]string, row, col int) bool { - return row >= 0 && row < len(grid) && col >= 0 && col <= len(grid) + return row >= 0 && row < len(grid) && col >= 0 && col < len(grid[0]) } func parseInput(input string) (ans [][]string) { diff --git a/2023/day10/main_test.go b/2023/day10/main_test.go index 73fdb05..c90d4ac 100644 --- a/2023/day10/main_test.go +++ b/2023/day10/main_test.go @@ -106,12 +106,12 @@ func Test_pipeMaze(t *testing.T) { part: 2, want: 10, }, - // { - // name: "actual part 2", - // input: input, - // part: 2, - // want: 0, - // }, + { + name: "actual part 2", + input: input, + part: 2, + want: 493, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {