From d9f24da95a820b0bf38b6a085a598b03dad896fc Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sat, 18 Dec 2021 12:22:01 -0500 Subject: [PATCH] 2021 day14, got confused with the recursive-stuff for part 2 :/ --- 2021/day14/main.go | 144 ++++++++++++++++++++++++++++++++++++++++ 2021/day14/main_test.go | 76 +++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 2021/day14/main.go create mode 100644 2021/day14/main_test.go diff --git a/2021/day14/main.go b/2021/day14/main.go new file mode 100644 index 0000000..4bfbb6f --- /dev/null +++ b/2021/day14/main.go @@ -0,0 +1,144 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "math" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathy" + "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") + } +} + +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) + } +} + +// naive/brute force solution that does not work for part 2 :( +func part1(input string) int { + template, rules := parseInput(input) + + for step := 0; step < 10; step++ { + sp := strings.Split(template, "") + var next []string + for i := 0; i < len(sp)-1; i++ { + two := sp[i] + sp[i+1] + rule := rules[two] + next = append(next, sp[i]) + next = append(next, rule) + } + next = append(next, sp[len(sp)-1]) + + template = strings.Join(next, "") + } + + // most common element minus least common element + most, least := 0, math.MaxInt32 + count := map[rune]int{} + for _, r := range template { + count[r]++ + } + for _, ct := range count { + if ct > most { + most = ct + } + if ct < least { + least = ct + } + } + + return most - least +} + +func part2(input string) int { + template, rules := parseInput(input) + + initialCount := map[string]int{} + for _, r := range template { + initialCount[string(r)]++ + } + + addlCountAfter40Steps := grow(template, 40, rules, map[string]map[string]int{}) + // have to add the initial template back in because grow only accounts for characters that are + // added in ADDITION to the passed in template + for k, v := range initialCount { + addlCountAfter40Steps[k] += v + } + + // most common element minus least common element + most, least := 0, math.MaxInt64 + for _, v := range addlCountAfter40Steps { + most = mathy.MaxInt(most, v) + least = mathy.MinInt(least, v) + } + return most - least +} + +func grow(template string, stepsLeft int, rules map[string]string, memo map[string]map[string]int) (addlCounts map[string]int) { + addlCounts = map[string]int{} + + if stepsLeft == 0 { + return addlCounts + } + + key := fmt.Sprint(template, stepsLeft) + if res, ok := memo[key]; ok { + return res + } + + for i := 0; i < len(template)-1; i++ { + pair := template[i : i+2] + between := rules[pair] + addlCounts[between]++ + + // get the additional characters for recursing just this three character section + // calling grow will memoize that result to eliminate duplicate work + recurse := grow(pair[:1]+between+pair[1:], stepsLeft-1, rules, memo) + + // merge those additional characters into this (parent function call) addlCounts map + for k, v := range recurse { + addlCounts[k] += v + } + } + + // store it, return it + memo[key] = addlCounts + return addlCounts +} + +func parseInput(input string) (template string, rules map[string]string) { + parts := strings.Split(input, "\n\n") + template = parts[0] + + rules = map[string]string{} + for _, line := range strings.Split(parts[1], "\n") { + sp := strings.Split(line, " -> ") + rules[sp[0]] = sp[1] + } + return template, rules +} diff --git a/2021/day14/main_test.go b/2021/day14/main_test.go new file mode 100644 index 0000000..5032b2f --- /dev/null +++ b/2021/day14/main_test.go @@ -0,0 +1,76 @@ +package main + +import ( + "testing" +) + +var example = `NNCB + +CH -> B +HH -> N +CB -> H +NH -> C +HB -> C +HC -> B +HN -> C +NN -> C +BH -> H +NC -> B +NB -> B +BN -> B +BB -> N +BC -> B +CC -> N +CN -> C` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 1588, + }, + { + name: "actual", + input: input, + want: 2851, + }, + } + 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: 2188189693529, + }, + { + name: "actual", + input: input, + want: 10002813279337, + }, + } + 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) + } + }) + } +}