Files
advent-of-code-go/2020/day19/main.go
T

160 lines
4.1 KiB
Go

package main
import (
"flag"
"fmt"
"regexp"
"strings"
"github.com/alexchao26/advent-of-code-go/cast"
"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)
}
// ~1200
func part1(input string) int {
graph, messages := parseInput(input)
fillInGraph(graph, 0)
var matchRuleZero int
for _, m := range messages {
for _, opt := range graph[0].resolved {
if m == opt {
matchRuleZero++
break
}
}
}
return matchRuleZero
}
// 1018 @ 1:19AM
func part2(input string) int {
graph, messages := parseInput(input)
// these are the dependencies of rules 8 & 11 (which are the only dependencies
// of rule 0). fill in the resolved fields for these graph nodes
// b/c these definitions are not circular (downstream of changes), they will
// not infinite loop
fillInGraph(graph, 42)
fillInGraph(graph, 31)
// generate regexp strings that will be used to match against rules 8 and 11
// and ultimate match rule 0
part42 := fmt.Sprintf("(%s)", strings.Join(graph[42].resolved, "|"))
part31 := fmt.Sprintf("(%s)", strings.Join(graph[31].resolved, "|"))
// rule 8 is essentially 1 or more instances of rule 42
rule8String := fmt.Sprintf("(%s)+", part42)
// note: i'm unaware of how to make two regexp portions have the same number
// of segments, so I made this helper function that changes that number
// then I just run it ten times because that should be large enough to cover
// any of the rules in the input...
makeRegexp := func(num int) *regexp.Regexp {
// rule 11 is an equal number of 42 and 31 rules
return regexp.MustCompile(fmt.Sprintf("^%s%s{%d}%s{%d}$", rule8String, part42, num, part31, num))
}
var matchRuleZero int
for _, m := range messages {
for i := 1; i < 10; i++ {
pattern := makeRegexp(i)
if pattern.MatchString(m) {
matchRuleZero++
break
}
}
}
return matchRuleZero
}
func fillInGraph(graph map[int]*rule, entry int) []string {
if len(graph[entry].resolved) != 0 {
// return a copy of resolved otherwise there's all kinds of side effect errors
return append([]string{}, graph[entry].resolved...)
}
// iterate through options, resolve children and append resolved paths
// for the current entry point
for _, option := range graph[entry].options {
// this will be all permutations generated from recursive calls to fillInGraph
// Note: there's probably a cleaner algorithm to do this kind of perm generation...
resolved := []string{""}
for _, entryPoint := range option {
nestedResolveVals := fillInGraph(graph, entryPoint)
var newResolved []string
for _, nextPiece := range nestedResolveVals {
for _, prev := range resolved {
newResolved = append(newResolved, prev+nextPiece)
}
}
resolved = newResolved
}
graph[entry].resolved = append(graph[entry].resolved, resolved...)
}
return graph[entry].resolved
}
type rule struct {
resolved []string
options [][]int
}
// Stringer interface for debugging
func (r rule) String() string {
var ans string
ans += fmt.Sprintf("OPTIONS: %v\n", r.options)
ans += fmt.Sprintf(" RESOLVED: %v\n", r.resolved)
return ans
}
func parseInput(input string) (rules map[int]*rule, messages []string) {
parts := strings.Split(input, "\n\n")
rules = map[int]*rule{}
for _, r := range strings.Split(parts[0], "\n") {
if regexp.MustCompile("[a-z]").MatchString(r) {
var num int
var char string
fmt.Sscanf(r, "%d: \"%1s\"", &num, &char)
rules[num] = &rule{resolved: []string{char}}
} else {
split := strings.Split(r, ": ")
key := cast.ToInt(split[0])
newRule := rule{}
for _, ruleNums := range strings.Split(split[1], " | ") {
nums := strings.Split(ruleNums, " ")
var option []int
for _, n := range nums {
option = append(option, cast.ToInt(n))
}
newRule.options = append(newRule.options, option)
}
rules[key] = &newRule
}
}
messages = strings.Split(parts[1], "\n")
return rules, messages
}