|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "os" |
| 6 | + "strings" |
| 7 | +) |
| 8 | + |
| 9 | +var dirCoords = map[string][]int{ |
| 10 | + "^": []int{-1, 0}, |
| 11 | + "v": []int{1, 0}, |
| 12 | + ">": []int{0, 1}, |
| 13 | + "<": []int{0, -1}, |
| 14 | +} |
| 15 | + |
| 16 | +var nextDirs = map[string]string{ |
| 17 | + "^": ">", |
| 18 | + "v": "<", |
| 19 | + ">": "v", |
| 20 | + "<": "^", |
| 21 | +} |
| 22 | + |
| 23 | +func copyGrid(grid [][]string) [][]string { |
| 24 | + c := [][]string{} |
| 25 | + for i := range len(grid) { |
| 26 | + row := []string{} |
| 27 | + for j := range len(grid[0]) { |
| 28 | + row = append(row, grid[i][j]) |
| 29 | + } |
| 30 | + c = append(c, row) |
| 31 | + } |
| 32 | + return c |
| 33 | +} |
| 34 | + |
| 35 | +func emptyVisited(n int) [][]map[string]bool { |
| 36 | + out := make([][]map[string]bool, n) // create the outer slice with space for 'rows' inner slices |
| 37 | + for i := range n { |
| 38 | + out[i] = make([]map[string]bool, n) // create each inner slice with 'cols' elements |
| 39 | + for j := range n { |
| 40 | + out[i][j] = make(map[string]bool) |
| 41 | + } |
| 42 | + } |
| 43 | + return out |
| 44 | +} |
| 45 | + |
| 46 | +func copyVisited(visited [][][]string) [][][]string { |
| 47 | + // Create a new slice with the same length as the original |
| 48 | + copied := make([][][]string, len(visited)) |
| 49 | + |
| 50 | + for i, subArray := range visited { |
| 51 | + // Create a new slice for each 2D sub-array |
| 52 | + copied[i] = make([][]string, len(subArray)) |
| 53 | + |
| 54 | + for j, innerArray := range subArray { |
| 55 | + // Create a new slice for each 1D inner-array |
| 56 | + copied[i][j] = make([]string, len(innerArray)) |
| 57 | + |
| 58 | + // Copy the strings from the original inner-array |
| 59 | + copy(copied[i][j], innerArray) |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + return copied |
| 64 | +} |
| 65 | + |
| 66 | +// updates grid in place by moving the arrow one step, updates visited in place with position at start of step |
| 67 | +// returns r, c, bool where bool indicates out of bounds if were to take a step, ie you are on the very edge |
| 68 | +func step(r, c int, gridPtr *[][]string, visited *[][]map[string]bool) (int, int, bool) { |
| 69 | + grid := *gridPtr |
| 70 | + curDir := grid[r][c] |
| 71 | + // update visited |
| 72 | + (*visited)[r][c][curDir] = true |
| 73 | + dirCoord := dirCoords[curDir] |
| 74 | + nextR, nextC := r+dirCoord[0], c+dirCoord[1] |
| 75 | + nextInBound := nextR >= 0 && nextR < len(grid) && nextC >= 0 && nextC < len(grid[0]) |
| 76 | + if !nextInBound { |
| 77 | + return r, c, true |
| 78 | + } |
| 79 | + // check for turn |
| 80 | + if grid[nextR][nextC] == "#" { |
| 81 | + // stay in place and turn |
| 82 | + turnedDir := nextDirs[curDir] |
| 83 | + grid[r][c] = turnedDir |
| 84 | + // since turned have to check if next step out of bounds |
| 85 | + dirCoord = dirCoords[turnedDir] |
| 86 | + nextR, nextC = r+dirCoord[0], c+dirCoord[1] |
| 87 | + nextInBound = nextR >= 0 && nextR < len(grid) && nextC >= 0 && nextC < len(grid[0]) |
| 88 | + if !nextInBound { |
| 89 | + return r, c, true |
| 90 | + } |
| 91 | + return r, c, false |
| 92 | + } |
| 93 | + // step in current direction |
| 94 | + grid[r][c] = "." |
| 95 | + grid[nextR][nextC] = curDir |
| 96 | + |
| 97 | + return nextR, nextC, false |
| 98 | +} |
| 99 | + |
| 100 | +// takes in a grid with a start location. traverses the grid till it exits or get in a loop. |
| 101 | +// returns true if has a loop |
| 102 | +func traverse(r, c int, origGrid [][]string) (hasLoop bool) { |
| 103 | + grid := copyGrid(origGrid) |
| 104 | + visited := emptyVisited(len(origGrid)) |
| 105 | + for { |
| 106 | + done := false |
| 107 | + r, c, done = step(r, c, &grid, &visited) |
| 108 | + if done { |
| 109 | + return false |
| 110 | + } |
| 111 | + |
| 112 | + curDir := grid[r][c] |
| 113 | + looped := visited[r][c][curDir] |
| 114 | + if looped { |
| 115 | + return true |
| 116 | + } |
| 117 | + } |
| 118 | + //r >= 0 && r < len(grid) && c >= 0 && c < len(grid[0]) |
| 119 | +} |
| 120 | + |
| 121 | +func part2() { |
| 122 | + raw, _ := os.ReadFile("input.txt") |
| 123 | + //raw, _ := os.ReadFile("test.txt") |
| 124 | + data := string(raw) |
| 125 | + |
| 126 | + grid := [][]string{} |
| 127 | + for _, row := range strings.Split(data, "\n") { |
| 128 | + grid = append(grid, strings.Split(row, "")) |
| 129 | + } |
| 130 | + |
| 131 | + startR := -1 |
| 132 | + startC := -1 |
| 133 | + for i := range len(grid) { |
| 134 | + for j := range len(grid[0]) { |
| 135 | + // find start |
| 136 | + if grid[i][j] == "^" { |
| 137 | + startR = i |
| 138 | + startC = j |
| 139 | + break |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + count := 0 |
| 145 | + // visited doesn't matter, we don't check it here |
| 146 | + visited := emptyVisited(len(grid)) |
| 147 | + gridCopy := copyGrid(grid) |
| 148 | + r := startR |
| 149 | + c := startC |
| 150 | + for { |
| 151 | + done := false |
| 152 | + r, c, done = step(r, c, &gridCopy, &visited) |
| 153 | + if done { |
| 154 | + fmt.Println(count) |
| 155 | + return |
| 156 | + } |
| 157 | + |
| 158 | + // imagine there is an obstacle in front of us |
| 159 | + withObstacle := copyGrid(gridCopy) |
| 160 | + curDir := withObstacle[r][c] |
| 161 | + // place obstacle, we know one can exist there since done is false |
| 162 | + dirCoord := dirCoords[curDir] |
| 163 | + obstacleR, obstacleC := r+dirCoord[0], c+dirCoord[1] |
| 164 | + nextInBound := obstacleR >= 0 && obstacleR < len(grid) && obstacleC >= 0 && obstacleC < len(grid[0]) |
| 165 | + if !nextInBound { |
| 166 | + fmt.Println(count) |
| 167 | + return |
| 168 | + } |
| 169 | + withObstacle[obstacleR][obstacleC] = "#" |
| 170 | + |
| 171 | + hasLoop := traverse(r, c, withObstacle) |
| 172 | + if hasLoop { |
| 173 | + count += 1 |
| 174 | + } |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +func main() { |
| 179 | + //part1() |
| 180 | + part2() |
| 181 | +} |
0 commit comments