2020-day19: oof 1200/1018. Crazy graph traversal for permutations, then hacking together regexp

This commit is contained in:
alexchao26
2020-12-19 01:42:50 -05:00
parent 0be8f0f018
commit 50fcc7d507
3 changed files with 200 additions and 21 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
.vscode/*
input.txt
input*.txt
prompt.*
+132 -18
View File
@@ -3,9 +3,11 @@ package main
import (
"flag"
"fmt"
"regexp"
"strings"
"github.com/alexchao26/advent-of-code-go/mathutil"
"github.com/alexchao26/advent-of-code-go/util"
)
@@ -15,32 +17,144 @@ func main() {
flag.Parse()
fmt.Println("Running part", part)
var ans int
if part == 1 {
ans := part1(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
ans = part1(util.ReadFile("./input.txt"))
} else {
ans := part2(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
ans = part2(util.ReadFile("./input.txt"))
}
fmt.Println("Output:", ans)
}
}
// ~1200
func part1(input string) int {
parsed := parseInput(input)
_ = parsed
graph, messages := parseInput(input)
return 0
}
fillInGraph(graph, 0)
func part2(input string) int {
return 0
}
func parseInput(input string) (ans []int) {
lines := strings.Split(input, "\n")
for _, l := range lines {
ans = append(ans, mathutil.StrToInt(l))
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 := mathutil.StrToInt(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, mathutil.StrToInt(n))
}
newRule.options = append(newRule.options, option)
}
rules[key] = &newRule
}
}
messages = strings.Split(parts[1], "\n")
return rules, messages
}
+67 -2
View File
@@ -2,15 +2,31 @@ package main
import (
"testing"
"github.com/alexchao26/advent-of-code-go/util"
)
var example = `0: 4 1 5
1: 2 3 | 3 2
2: 4 4 | 5 5
3: 4 5 | 5 4
4: "a"
5: "b"
ababbb
bababa
abbbab
aaabbb
aaaabbb`
func Test_part1(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
// {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER},
{"example", example, 2},
{"actual", util.ReadFile("input.txt"), 136},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -21,13 +37,62 @@ func Test_part1(t *testing.T) {
}
}
var example2 = `42: 9 14 | 10 1
9: 14 27 | 1 26
10: 23 14 | 28 1
1: "a"
11: 42 31 | 42 11 31
5: 1 14 | 15 1
19: 14 1 | 14 14
12: 24 14 | 19 1
16: 15 1 | 14 14
31: 14 17 | 1 13
6: 14 14 | 1 14
2: 1 24 | 14 4
0: 8 11
13: 14 3 | 1 12
15: 1 | 14
17: 14 2 | 1 7
23: 25 1 | 22 14
28: 16 1
4: 1 1
20: 14 14 | 1 15
3: 5 14 | 16 1
27: 1 6 | 14 18
14: "b"
21: 14 1 | 1 14
25: 1 1 | 1 14
22: 14 14
8: 42 | 42 8
26: 14 22 | 1 20
18: 15 15
7: 14 5 | 1 21
24: 14 1
abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa
bbabbbbaabaabba
babbbbaabbbbbabbbbbbaabaaabaaa
aaabbbbbbaaaabaababaabababbabaaabbababababaaa
bbbbbbbaaaabbbbaaabbabaaa
bbbababbbbaaaaaaaabbababaaababaabab
ababaaaaaabaaab
ababaaaaabbbaba
baabbaaaabbaaaababbaababb
abbbbabbbbaaaababbbbbbaaaababb
aaaaabbaabaaaaababaa
aaaabbaaaabbaaa
aaaabbaabbaaaaaaabbbabbbaaabbaabaaa
babaaabbbaaabaababbaabababaaab
aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba`
func Test_part2(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
// {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER},
{"example", example2, 12},
{"actual", util.ReadFile("input.txt"), 256},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {