diff --git a/2017/day12/main.go b/2017/day12/main.go new file mode 100644 index 0000000..8c22e42 --- /dev/null +++ b/2017/day12/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "flag" + "fmt" + "strings" + + "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) + + var ans int + if part == 1 { + ans = part1(util.ReadFile("./input.txt")) + } else { + ans = part2(util.ReadFile("./input.txt")) + } + fmt.Println("Output:", ans) +} + +func part1(input string) int { + graph := makeGraphFromInput(input) + + var count int + for k := range graph { + if dfsCanReachTarget(graph, k, 0, map[int]bool{}) { + count++ + } + } + + return count +} + +func part2(input string) int { + graph := makeGraphFromInput(input) + + allKeys := []int{} + for k := range graph { + allKeys = append(allKeys, k) + } + + var groupCount int + // nodes that have been added to a group (that has been counted) + hasBeenGrouped := map[int]bool{} + + for target := range graph { + if !hasBeenGrouped[target] { + // iterate through all graph nodes and check if they can be reached + for k := range graph { + // performance optimization: skip nodes that are already grouped + if k != target && !hasBeenGrouped[k] { + // if this group can reach the target, they're part of that group + if dfsCanReachTarget(graph, k, target, map[int]bool{}) { + hasBeenGrouped[k] = true + } + } + } + groupCount++ + } + } + + return groupCount +} + +func dfsCanReachTarget(graph map[int][]int, entry int, target int, visited map[int]bool) bool { + // break infinite loops + if visited[entry] { + return false + } + visited[entry] = true + + for _, child := range graph[entry] { + if child == target || dfsCanReachTarget(graph, child, target, visited) { + return true + } + } + // default to returning false + return false +} + +func makeGraphFromInput(input string) map[int][]int { + lines := strings.Split(input, "\n") + graph := make(map[int][]int, len(lines)) + for _, l := range lines { + parts := strings.Split(l, " <-> ") + ID := mathutil.StrToInt(parts[0]) + for _, child := range strings.Split(parts[1], ", ") { + graph[ID] = append(graph[ID], mathutil.StrToInt(child)) + } + } + return graph +} diff --git a/2017/day12/main_test.go b/2017/day12/main_test.go new file mode 100644 index 0000000..8d2f1ef --- /dev/null +++ b/2017/day12/main_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "testing" + "time" + + "github.com/alexchao26/advent-of-code-go/util" +) + +var example = `0 <-> 2 +1 <-> 1 +2 <-> 0, 3, 4 +3 <-> 2, 4 +4 <-> 2, 3, 6 +5 <-> 6 +6 <-> 4, 5` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + {"example", example, 6}, + {"actual", util.ReadFile("input.txt"), 169}, + } + 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 + }{ + {"example", example, 2}, + {"actual", util.ReadFile("input.txt"), 179}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + startTime := time.Now() + if got := part2(tt.input); got != tt.want { + t.Errorf("part2() = %v, want %v", got, tt.want) + } + t.Logf("Run time for %s: %v", tt.name, time.Since(startTime)) + }) + } +}