diff --git a/2021/day12/main.go b/2021/day12/main.go new file mode 100644 index 0000000..e679a70 --- /dev/null +++ b/2021/day12/main.go @@ -0,0 +1,153 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/util" +) + +//go:embed input.txt +var input string + +func init() { + // do this in init (not main) so test file has same input + input = strings.TrimRight(input, "\n") + if len(input) == 0 { + panic("empty input.txt file") + } +} + +// 610/1663 +func main() { + var part int + flag.IntVar(&part, "part", 1, "part 1 or 2") + flag.Parse() + fmt.Println("Running part", part) + + if part == 1 { + ans := part1(input) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := part2(input) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func part1(input string) int { + parsed := parseInput(input) + + // part 1 + // cannot visit SMALL caves more than once (lowercase ones) + // how many paths are there from start -> end + + graph := map[string]map[string]bool{} + for _, pair := range parsed { + if graph[pair[0]] == nil { + graph[pair[0]] = map[string]bool{} + } + if graph[pair[1]] == nil { + graph[pair[1]] = map[string]bool{} + } + graph[pair[0]][pair[1]] = true + graph[pair[1]][pair[0]] = true + } + + return walk(graph, "start", map[string]bool{"start": true}, []string{"start"}) +} + +func walk(graph map[string]map[string]bool, current string, visited map[string]bool, path []string) int { + if current == "end" { + return 1 + } + + var pathsToEnd int + + for visitable := range graph[current] { + if visited[visitable] && strings.ToUpper(visitable) != visitable { + continue + } + visited[current] = true + // path is basically unused (and wasting memory), but useful to debug sometimes + path = append(path, visitable) + + pathsToEnd += walk(graph, visitable, visited, path) + + // backtrack + visited[visitable] = false + path = path[:len(path)-1] + } + + return pathsToEnd +} + +func part2(input string) int { + parsed := parseInput(input) + + // cannot visit SMALL caves more than once (lowercase ones) + // how many paths are there from start -> end + + graph := map[string]map[string]bool{} + for _, pair := range parsed { + if graph[pair[0]] == nil { + graph[pair[0]] = map[string]bool{} + } + if graph[pair[1]] == nil { + graph[pair[1]] = map[string]bool{} + } + graph[pair[0]][pair[1]] = true + graph[pair[1]][pair[0]] = true + } + + return walk2(graph, "start", map[string]int{"start": 5}, []string{"start"}, false) +} + +func walk2(graph map[string]map[string]bool, current string, visited map[string]int, path []string, doubleUsed bool) int { + if current == "end" { + fmt.Println("path", path) + return 1 + } + + visited[current]++ + + var pathsToEnd int + + for visitable := range graph[current] { + if visitable == "start" { + continue + } + + if strings.ToUpper(visitable) != visitable && visited[visitable] > 0 { + if doubleUsed { + continue + } else { + doubleUsed = true + } + } + + path = append(path, visitable) + pathsToEnd += walk2(graph, visitable, visited, path, doubleUsed) + + // backtrack + visited[visitable]-- + path = path[:len(path)-1] + // backtrack doubleUsed IF this is a smallcave and reducing its visited count still has the + // cave marked as visited (aka count == 1) + if strings.ToUpper(visitable) != visitable && visited[visitable] == 1 { + doubleUsed = false + } + } + + return pathsToEnd +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "-")) + } + return ans +} diff --git a/2021/day12/main_test.go b/2021/day12/main_test.go new file mode 100644 index 0000000..0f911e1 --- /dev/null +++ b/2021/day12/main_test.go @@ -0,0 +1,115 @@ +package main + +import ( + "testing" +) + +var example = `start-A +start-b +A-c +A-b +b-d +A-end +b-end` + +var example2 = `dc-end +HN-start +start-kj +dc-start +dc-HN +LN-dc +HN-end +kj-sa +kj-HN +kj-dc` + +var example3 = `fs-end +he-DX +fs-he +start-DX +pj-DX +end-zg +zg-sl +zg-pj +pj-he +RW-he +fs-DX +pj-RW +zg-RW +start-pj +he-WI +zg-he +pj-fs +start-RW` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 10, + }, + { + name: "example2", + input: example2, + want: 19, + }, + { + name: "example3", + input: example3, + want: 226, + }, + { + name: "actual", + input: input, + want: 3421, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part1(tt.input); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 36, + }, + { + name: "example2", + input: example2, + want: 103, + }, + { + name: "example3", + input: example3, + want: 3509, + }, + { + name: "actual", + input: input, + want: 84870, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part2(tt.input); got != tt.want { + t.Errorf("part2() = %v, want %v", got, tt.want) + } + }) + } +}