From b20b1c44b3f43ab1e53312d01d8d9bc7bb267615 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 25 Dec 2020 15:16:37 -0500 Subject: [PATCH] 2016-day24: bfs to make weighted graph -> dfs to find shortest path through all nodes --- 2016/day24/main.go | 137 ++++++++++++++++++++++++++++++++++++++++ 2016/day24/main_test.go | 33 ++++++++++ 2 files changed, 170 insertions(+) create mode 100644 2016/day24/main.go create mode 100644 2016/day24/main_test.go diff --git a/2016/day24/main.go b/2016/day24/main.go new file mode 100644 index 0000000..64483cf --- /dev/null +++ b/2016/day24/main.go @@ -0,0 +1,137 @@ +package main + +import ( + "flag" + "fmt" + "math" + "regexp" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathutil" + "github.com/alexchao26/advent-of-code-go/util" +) + +func main() { + var part int + flag.IntVar(&part, "part", 1, "part 1 or 2") + flag.Parse() + fmt.Println("Running part", part) + + ans := cleaningRobot(util.ReadFile("./input.txt"), part) + fmt.Println("Output:", ans) +} + +func cleaningRobot(input string, part int) int { + var grid [][]string + for _, l := range strings.Split(input, "\n") { + grid = append(grid, strings.Split(l, "")) + } + + // bfs from every numbered cell to every other + // generate a weighted graph + var graph [][]int + for r, row := range grid { + for c, cell := range row { + if regexp.MustCompile("[0-9]").MatchString(cell) { + poi := cell + distancesFromPOI := bfsGetEdgeWeights(grid, [2]int{r, c}) + // initialize graph size + if graph == nil { + for i := 0; i < len(distancesFromPOI); i++ { + graph = append(graph, make([]int, len(distancesFromPOI))) + } + } + graph[cast.ToInt(poi)] = distancesFromPOI + } + } + } + + // then a recursive, backtracking dfs on that weighted graph to determine + // the shortest total path + returnToZero := part != 1 + return dfs(graph, 0, map[int]bool{0: true}, returnToZero) +} + +type bfsNode struct { + row, col int // 0,0 is top left + distance int +} + +// allows passing through points of interest +func bfsGetEdgeWeights(grid [][]string, start [2]int) []int { + // points of interest to distance to reach them from the starting coord + poiToDistance := map[string]int{ + grid[start[0]][start[1]]: 0, + } + // run until all nodes have been seen... + queue := []bfsNode{ + {start[0], start[1], 0}, + } + visited := map[[2]int]bool{} + for len(queue) > 0 { + front := queue[0] + queue = queue[1:] + + if visited[[2]int{front.row, front.col}] { + continue + } + visited[[2]int{front.row, front.col}] = true + + if regexp.MustCompile("[0-9]").MatchString(grid[front.row][front.col]) { + poiToDistance[grid[front.row][front.col]] = front.distance + } + for _, d := range dirs { + nextRow, nextCol := front.row+d[0], front.col+d[1] + // don't need to check for going out of bounds because there are walls + // surrounding everything + if grid[nextRow][nextCol] != "#" { + queue = append(queue, bfsNode{ + row: nextRow, + col: nextCol, + distance: front.distance + 1, + }) + } + } + + } + + distances := make([]int, len(poiToDistance)) + for numStr, dist := range poiToDistance { + distances[cast.ToInt(numStr)] = dist + } + return distances +} + +var dirs = [][2]int{ + {0, -1}, + {0, 1}, + {1, 0}, + {-1, 0}, +} + +func dfs(graph [][]int, entryIndex int, visited map[int]bool, returnToZero bool) (minWeightSum int) { + // if all nodes have been visited, return zero for part 1, or the distance + // from the entryIndex to the zero POI + if len(graph) == len(visited) { + if returnToZero { + return graph[entryIndex][0] + } + return 0 + } + + // get the minimum distance from a recursive call + minDistance := math.MaxInt32 + for i, val := range graph[entryIndex] { + if !visited[i] { + visited[i] = true + + dist := val + dfs(graph, i, visited, returnToZero) + minDistance = mathutil.MinInt(minDistance, dist) + + delete(visited, i) + } + } + + return minDistance +} diff --git a/2016/day24/main_test.go b/2016/day24/main_test.go new file mode 100644 index 0000000..8d15030 --- /dev/null +++ b/2016/day24/main_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "testing" + + "github.com/alexchao26/advent-of-code-go/util" +) + +var example = `########### +#0.1.....2# +#.#######.# +#4.......3# +###########` + +func Test_cleaningRobot(t *testing.T) { + tests := []struct { + name string + input string + part int + want int + }{ + {"part1 example", example, 1, 14}, + {"part1 actual", util.ReadFile("input.txt"), 1, 442}, + {"part2 actual", util.ReadFile("input.txt"), 2, 660}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := cleaningRobot(tt.input, tt.part); got != tt.want { + t.Errorf("cleaningRobot() = %v, want %v", got, tt.want) + } + }) + } +}