From be32018be9063bf3d6b1e1ab49e5203331a93a6f Mon Sep 17 00:00:00 2001 From: James Griffin Date: Sun, 12 Dec 2021 23:26:23 +0000 Subject: [PATCH] Solution for Day 12 --- README.md | 9 ++ main.go | 3 + twelve/input.txt | 21 ++++ twelve/main.go | 24 ++++ twelve/navigation.go | 240 ++++++++++++++++++++++++++++++++++++++ twelve/navigation_test.go | 80 +++++++++++++ twelve/test_input.txt | 7 ++ twelve/test_input2.txt | 10 ++ twelve/test_input3.txt | 18 +++ 9 files changed, 412 insertions(+) create mode 100644 twelve/input.txt create mode 100644 twelve/main.go create mode 100644 twelve/navigation.go create mode 100644 twelve/navigation_test.go create mode 100644 twelve/test_input.txt create mode 100644 twelve/test_input2.txt create mode 100644 twelve/test_input3.txt diff --git a/README.md b/README.md index 9a36a65..652cb3b 100644 --- a/README.md +++ b/README.md @@ -120,3 +120,12 @@ The solution for "eleven" is: There were 1669 flashes after 100 steps They all flash on step 351 ``` + +### Day twelve + +```sh +$ ./aoc2021 --twelve +The solution for "twelve" is: +Found 5157 paths through the cave system +Found 144309 paths through the cave system +``` diff --git a/main.go b/main.go index 9245674..ce9abe2 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "unsupervised.ca/aoc2021/six" "unsupervised.ca/aoc2021/ten" "unsupervised.ca/aoc2021/three" + "unsupervised.ca/aoc2021/twelve" "unsupervised.ca/aoc2021/two" ) @@ -55,6 +56,8 @@ func main() { day = ten.Init("ten/input.txt") case "eleven": day = eleven.Init("eleven/input.txt") + case "twelve": + day = twelve.Init("twelve/input.txt") default: fmt.Printf("%q does not have a solution.\n", flagParts[1]) help() diff --git a/twelve/input.txt b/twelve/input.txt new file mode 100644 index 0000000..41c622d --- /dev/null +++ b/twelve/input.txt @@ -0,0 +1,21 @@ +TR-start +xx-JT +xx-TR +hc-dd +ab-JT +hc-end +dd-JT +ab-dd +TR-ab +vh-xx +hc-JT +TR-vh +xx-start +hc-ME +vh-dd +JT-bm +end-ab +dd-xx +end-TR +hc-TR +start-vh \ No newline at end of file diff --git a/twelve/main.go b/twelve/main.go new file mode 100644 index 0000000..1f34c0c --- /dev/null +++ b/twelve/main.go @@ -0,0 +1,24 @@ +package twelve + +import "fmt" + +type Twelve struct { + navigation navigation +} + +func Init(filepath string) *Twelve { + twelve := &Twelve{ + navigation: navigation{}, + } + + twelve.navigation.load(filepath) + return twelve +} + +func (d *Twelve) Answer() string { + return fmt.Sprintf("Found %d paths through the cave system", len(d.navigation.findPaths())) +} + +func (d *Twelve) FollowUp() string { + return fmt.Sprintf("Found %d paths through the cave system", len(d.navigation.findPaths2())) +} diff --git a/twelve/navigation.go b/twelve/navigation.go new file mode 100644 index 0000000..23c091b --- /dev/null +++ b/twelve/navigation.go @@ -0,0 +1,240 @@ +package twelve + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +type caveSize int + +const ( + startLabel = "start" + endLabel = "end" + small caveSize = 0 + large caveSize = 1 +) + +type cave struct { + label string + connections []*cave + size caveSize +} + +type navigation struct { + input []string + start *cave + end *cave + caves map[string]*cave +} + +func (n *navigation) load(filename string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() + + n.caves = map[string]*cave{} + scanner := bufio.NewScanner(file) + for scanner.Scan() { + n.input = append(n.input, scanner.Text()) + caves := strings.Split(scanner.Text(), "-") + + if len(caves) != 2 { + return fmt.Errorf("expected 2 caves connected, found %s", scanner.Text()) + } + + if n.caves[caves[0]] == nil { + n.caves[caves[0]] = &cave{ + label: caves[0], + size: size(caves[0]), + } + } + + if n.caves[caves[1]] == nil { + n.caves[caves[1]] = &cave{ + label: caves[1], + size: size(caves[1]), + } + } + + // Add connection to 1 on 0 + found := false + for _, c := range n.caves[caves[0]].connections { + if c.label == caves[1] { + found = true + } + } + if !found { + n.caves[caves[0]].connections = append(n.caves[caves[0]].connections, n.caves[caves[1]]) + } + + // Add connection to 0 on 1 + found = false + for _, c := range n.caves[caves[1]].connections { + if c.label == caves[0] { + found = true + } + } + if !found && caves[0] != startLabel { + n.caves[caves[1]].connections = append(n.caves[caves[1]].connections, n.caves[caves[0]]) + } + } + // Initialize start/end + n.findStart() + n.findEnd() + + return nil +} + +func size(label string) caveSize { + if strings.ToUpper(label) == label { + return large + } + + return small +} + +func (n *navigation) findStart() *cave { + if n.start != nil { + return n.start + } + + for k := range n.caves { + if k == startLabel { + n.start = n.caves[k] + } + } + + return n.start +} + +func (n *navigation) findEnd() *cave { + if n.end != nil { + return n.end + } + + for k := range n.caves { + if k == endLabel { + n.end = n.caves[k] + } + } + + return n.end +} + +func (n *navigation) findPaths() [][]string { + s := n.findStart() + foundPaths := [][]string{} + + for _, c := range s.connections { + paths := n.findPathsRecursive([]string{s.label}, c, 1) + for _, p := range paths { + labels := []string{"start"} + for _, c := range p { + labels = append(labels, c.label) + } + foundPaths = append(foundPaths, labels) + } + } + + return foundPaths +} + +func (n *navigation) findPaths2() [][]string { + s := n.findStart() + foundPaths := [][]string{} + + for _, c := range s.connections { + paths := n.findPathsRecursive([]string{s.label}, c, 2) + for _, p := range paths { + labels := []string{"start"} + for _, c := range p { + labels = append(labels, c.label) + } + foundPaths = append(foundPaths, labels) + } + } + + // for k := range n.caves { + // fmt.Println(n.caves[k]) + // } + + // for _, p := range foundPaths { + // fmt.Println(p) + // } + + return foundPaths +} + +func (n *navigation) findPathsRecursive(from []string, in *cave, smallCaveLimit int) [][]*cave { + paths := [][]*cave{} + + if in == n.end { + paths = append(paths, []*cave{ + n.end, + }) + return paths + } + + for _, c := range in.connections { + if c.label == n.start.label { + continue + } + + // No back and forth shenanigans with small caves! + // Check if we have already been to the small cave + if c.size == small { + connectionOption := true + smallVisits := map[string]int{} + for _, l := range from { + if size(l) == small { + smallVisits[l]++ + } + } + + if in.size == small { + smallVisits[in.label]++ + } + + if smallCaveLimit > 1 { + for k := range smallVisits { + if smallVisits[k] == smallCaveLimit { + connectionOption = false + } + } + } + + if smallVisits[c.label] == smallCaveLimit { + connectionOption = false + } + + if smallVisits[c.label] == 0 { + connectionOption = true + } + + if !connectionOption { + continue + } + } + + pathsFromHere := n.findPathsRecursive(append(from, in.label), c, smallCaveLimit) + if len(pathsFromHere) == 0 { + continue + } + + for _, path := range pathsFromHere { + // Add the current to the front of each new path + newOption := []*cave{ + in, + } + newOption = append(newOption, path...) + + paths = append(paths, newOption) + } + } + + return paths +} diff --git a/twelve/navigation_test.go b/twelve/navigation_test.go new file mode 100644 index 0000000..df2b8e1 --- /dev/null +++ b/twelve/navigation_test.go @@ -0,0 +1,80 @@ +package twelve + +import ( + "testing" +) + +func Test_read(t *testing.T) { + n := navigation{} + if err := n.load("test_input.txt"); err != nil { + t.Log(err) + t.FailNow() + } + + if len(n.input) != 7 { + t.Logf("Expected 7 inputs, found %d", len(n.input)) + t.Fail() + } + + if len(n.caves) != 6 { + t.Logf("Expected 6 caves, found %d", len(n.caves)) + t.Fail() + } +} + +func Test_findPathsInput1(t *testing.T) { + n := navigation{} + n.load("test_input.txt") + + paths := n.findPaths() + + if len(paths) != 10 { + t.Logf("Expected 10 paths, found %d", len(paths)) + t.Fail() + } + + paths = n.findPaths2() + + if len(paths) != 36 { + t.Logf("Expected 36 paths, found %d", len(paths)) + t.Fail() + } +} + +func Test_findPathsInput2(t *testing.T) { + n := navigation{} + n.load("test_input2.txt") + + paths := n.findPaths() + + if len(paths) != 19 { + t.Logf("Expected 19 paths, found %d", len(paths)) + t.Fail() + } + + paths = n.findPaths2() + + if len(paths) != 103 { + t.Logf("Expected 103 paths, found %d", len(paths)) + t.Fail() + } +} + +func Test_findPathsInput3(t *testing.T) { + n := navigation{} + n.load("test_input3.txt") + + paths := n.findPaths() + + if len(paths) != 226 { + t.Logf("Expected 226 paths, found %d", len(paths)) + t.Fail() + } + + paths = n.findPaths2() + + if len(paths) != 3509 { + t.Logf("Expected 3509 paths, found %d", len(paths)) + t.Fail() + } +} diff --git a/twelve/test_input.txt b/twelve/test_input.txt new file mode 100644 index 0000000..898cd56 --- /dev/null +++ b/twelve/test_input.txt @@ -0,0 +1,7 @@ +start-A +start-b +A-c +A-b +b-d +A-end +b-end \ No newline at end of file diff --git a/twelve/test_input2.txt b/twelve/test_input2.txt new file mode 100644 index 0000000..ef30b81 --- /dev/null +++ b/twelve/test_input2.txt @@ -0,0 +1,10 @@ +dc-end +HN-start +start-kj +dc-start +dc-HN +LN-dc +HN-end +kj-sa +kj-HN +kj-dc \ No newline at end of file diff --git a/twelve/test_input3.txt b/twelve/test_input3.txt new file mode 100644 index 0000000..da6e083 --- /dev/null +++ b/twelve/test_input3.txt @@ -0,0 +1,18 @@ +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 \ No newline at end of file