mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
2021 day14, got confused with the recursive-stuff for part 2 :/
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user