diff --git a/.github/README.md b/.github/README.md index 9da9205..4cad363 100644 --- a/.github/README.md +++ b/.github/README.md @@ -34,7 +34,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have | [16](/16-ticketTranslation) | ![Partially complete][partial] | [Link](/16-ticketTranslation/python) | | | [17](/17-conwayCubes) | ![Partially complete][partial] | [Link](/17-conwayCubes/python) | | | [18](/18-operationOrder) | ![Partially complete][partial] | [Link](/18-operationOrder/python) | | -| [19](/19-monsterMessages) | ![Partially complete][partial] | [Link](/19-monsterMessages/python) | | +| [19](/19-monsterMessages) | ![Completed][check] | [Link](/19-monsterMessages/python) | [Link](/19-monsterMessages/go) | | 20 | | | | | 21 | | | | | 22 | | | | diff --git a/19-monsterMessages/README.md b/19-monsterMessages/README.md index 5ee6707..29160dd 100644 --- a/19-monsterMessages/README.md +++ b/19-monsterMessages/README.md @@ -27,6 +27,18 @@ Test cases 1.1 pass 2.1 pass +Answers +Part 1: 222 +Part 2: 339 + +❯ go run .\go\ +AoC 2020: day 19 - Monster Messages +Go go1.15.2 + +Test cases +1.1 pass +2.1 pass + Answers Part 1: 222 Part 2: 339 diff --git a/19-monsterMessages/go/challenge/common.go b/19-monsterMessages/go/challenge/common.go new file mode 100644 index 0000000..8c2cb93 --- /dev/null +++ b/19-monsterMessages/go/challenge/common.go @@ -0,0 +1,111 @@ +package challenge + +import ( + "regexp" + "strconv" + "strings" +) + +var ( + charRegex = regexp.MustCompile(`^\"([a-z])\"$`) // group 1 contains interesting information + orRuleRegex = regexp.MustCompile(`^((\d+ ?)+) \| ((\d+ ?)+)$`) // group 1 and 3 contain interesting info + singleRuleRegex = regexp.MustCompile(`^((\d+ ?)+)$`) // group 0 and 1 both contain the same interesting information +) + +const replaceMarker = "!!!REPLACETHISMARKER!!!" + +func generateCompoundRuleRegex(ruleset map[int]string, compound string, current int) string { + + // Used when you have a rule that looks like `125: 116 33 | 131 113` + // compound should be a sequence like `116 33` + // current should be the current rule number + + var o string + + for _, xs := range strings.Split(compound, " ") { + x, err := strconv.Atoi(xs) + if err != nil { + panic(err) + } + if x == current { + if x == 8 { + o += generateRuleRegex(ruleset, 42) + o += "+" + } else { + o += replaceMarker + } + } else { + o += generateRuleRegex(ruleset, x) + } + } + + return o +} + +func generateRuleRegex(ruleset map[int]string, rule int) string { + + // Rule is the integer number of the rule to generate a regular expression for + // Said integer should correspond to a key-value pair in the ruleset + + ruleContent := ruleset[rule] + + if charRegex.MatchString(ruleContent) { + // is a single character + // `116: "a"` + return charRegex.FindAllStringSubmatch(ruleContent, -1)[0][1] + } + + // if we get here, that means there are multiple options for the rule + // hence we need to open a new set of brackets + output := "(" + + if orRuleRegex.MatchString(ruleContent) { + // rule is an OR rule + // `83: 26 131 | 47 116` + submatches := orRuleRegex.FindAllStringSubmatch(ruleContent, -1)[0] + + output += generateCompoundRuleRegex(ruleset, submatches[1], rule) + output += "|" + output += generateCompoundRuleRegex(ruleset, submatches[3], rule) + } else if singleRuleRegex.MatchString(ruleContent) { + // rule is a simple sequence rule + // `92: 67 131` + output += generateCompoundRuleRegex(ruleset, singleRuleRegex.FindAllStringSubmatch(ruleContent, -1)[0][1], rule) + } + + // close original set of brackets + output += ")" + + return output +} + +func makeRulesetRegex(ruleset map[int]string) string { + return "^" + generateRuleRegex(ruleset, 0) + "$" +} + +func run(messages []string, regex *regexp.Regexp) int { + var validInputs int + for _, message := range messages { + if regex.MatchString(message) { + validInputs += 1 + } + } + return validInputs +} + +func parse(instr string) (map[int]string, []string) { + + splitInput := strings.Split(strings.TrimSpace(instr), "\n\n") + + rules := make(map[int]string) + for _, x := range strings.Split(splitInput[0], "\n") { + splitRule := strings.Split(x, ":") + num, err := strconv.Atoi(strings.TrimSpace(splitRule[0])) + if err != nil { + panic(err) + } + rules[num] = strings.TrimSpace(splitRule[1]) + } + + return rules, strings.Split(strings.TrimSpace(splitInput[1]), "\n") +} diff --git a/19-monsterMessages/go/challenge/partOne.go b/19-monsterMessages/go/challenge/partOne.go new file mode 100644 index 0000000..ff5c028 --- /dev/null +++ b/19-monsterMessages/go/challenge/partOne.go @@ -0,0 +1,9 @@ +package challenge + +import "regexp" + +func PartOne(instr string) int { + rules, messages := parse(instr) + ruleRegex := regexp.MustCompile(makeRulesetRegex(rules)) + return run(messages, ruleRegex) +} diff --git a/19-monsterMessages/go/challenge/partTwo.go b/19-monsterMessages/go/challenge/partTwo.go new file mode 100644 index 0000000..e7f54b6 --- /dev/null +++ b/19-monsterMessages/go/challenge/partTwo.go @@ -0,0 +1,29 @@ +package challenge + +import ( + "regexp" + "strings" +) + +func PartTwo(instr string) int { + + // patch input + instr = strings.ReplaceAll(instr, "8: 42", "8: 42 | 42 8") + instr = strings.ReplaceAll(instr, "11: 42 31", "11: 42 31 | 42 11 31") + + rules, messages := parse(instr) + + // Since we have sections of our ruleset, we have markers in the regex returned by `make_ruleset_regex` + // that denote where we need to insert a copy of the rule 11 regular expression (which also happens to + // have one of those markers in it) + + rr := makeRulesetRegex(rules) + elevenRegex := generateRuleRegex(rules, 11) + for i := 0; i < 10; i += 2 { + rr = strings.ReplaceAll(rr, replaceMarker, elevenRegex) + } + + // run as usual + ruleRegex := regexp.MustCompile(rr) + return run(messages, ruleRegex) +} diff --git a/19-monsterMessages/go/main.go b/19-monsterMessages/go/main.go new file mode 100644 index 0000000..343abfb --- /dev/null +++ b/19-monsterMessages/go/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "adventOfCode/19-monsterMessages/go/challenge" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "runtime" + "strconv" + + "github.com/fatih/color" +) + +func main() { + var infoStruct info + { + inb, err := ioutil.ReadFile("info.json") + if err != nil { + fmt.Println("Error: could not open info.json") + os.Exit(-1) + } + err = json.Unmarshal(inb, &infoStruct) + if err != nil { + fmt.Println("Error: could not parse info.json") + os.Exit(-1) + } + } + + var ( + year = infoStruct.Year + day = infoStruct.Day + title = infoStruct.Title + ) + + fmt.Fprintf(color.Output, "%s: day %s - %s\n", color.YellowString("AoC "+year), color.BlueString(day), title) + fmt.Fprintf(color.Output, "Go %s\n\n", color.BlueString(runtime.Version())) + + var challengeInput string + { + inb, err := ioutil.ReadFile("input.txt") + if err != nil { + fmt.Println("Error: could not open input.txt") + os.Exit(-1) + } + challengeInput = string(inb) + } + + runTests(infoStruct) + + fmt.Println("Answers") + fmt.Fprintf(color.Output, "Part %s: %s\n", color.BlueString("1"), color.BlueString(strconv.Itoa(challenge.PartOne(challengeInput)))) + fmt.Fprintf(color.Output, "Part %s: %s\n", color.BlueString("2"), color.BlueString(strconv.Itoa(challenge.PartTwo(challengeInput)))) + +} + +type tc struct { + Input string `json:"input"` + Expected int `json:"expected"` +} + +type info struct { + Year string `json:"year"` + Day string `json:"day"` + Title string `json:"title"` + TestCases struct { + One []tc `json:"one"` + Two []tc `json:"two"` + } `json:"testCases"` +} + +func runTests(infoStruct info) { + + if len(infoStruct.TestCases.One) == 0 && len(infoStruct.TestCases.Two) == 0 { + fmt.Println("Info: no test cases specified. Skipping tests") + fmt.Println() + return + } + + fmt.Println("Test cases") + + rt := func(tcs []tc, f func(string) int, n string) { + for i, tc := range tcs { + fmt.Fprintf(color.Output, "%s ", color.BlueString(n+"."+strconv.Itoa(i+1))) + result := f(tc.Input) + if result == tc.Expected { + fmt.Fprintf(color.Output, "%s", color.GreenString("pass")) + } else { + fmt.Fprintf(color.Output, "%s (got %s, expected %s)", color.RedString("fail"), color.BlueString(strconv.Itoa(result)), color.BlueString(strconv.Itoa(tc.Expected))) + } + fmt.Println() + } + } + + rt(infoStruct.TestCases.One, challenge.PartOne, "1") + rt(infoStruct.TestCases.Two, challenge.PartTwo, "2") + + fmt.Println() + +} diff --git a/19-monsterMessages/python/partTwo.py b/19-monsterMessages/python/partTwo.py index e574776..5fa1c15 100644 --- a/19-monsterMessages/python/partTwo.py +++ b/19-monsterMessages/python/partTwo.py @@ -2,13 +2,6 @@ from common import * import re -def recurse_replace(instr: str, old: str, new: str, limit: int, n: int = 0) -> str: - # I *think* this is tail recursive? - if n + 1 == limit: - return instr - return recurse_replace(instr.replace(old, new), old, new, limit, n=n + 1) - - def partTwo(instr: str) -> int: # patch input diff --git a/19-monsterMessages/test.py b/19-monsterMessages/test.py deleted file mode 100644 index 465b327..0000000 --- a/19-monsterMessages/test.py +++ /dev/null @@ -1,74 +0,0 @@ -import re -from typing import Dict -import time - -char_regex = re.compile(r"^\"([a-z])\"$") # group 1 contains interesting information -or_rule_regex = re.compile(r"^((\d+ ?)+) \| ((\d+ ?)+)$") # group 1 and 3 contain interesting info -single_rule_regex = re.compile(r"^((\d+ ?)+)$") # group 0 and 1 both contain the same interesting information - -rules = { - 0: "4 1 5", - 1: "2 3 | 3 2", - 2: "4 4 | 5 5", - 3: "4 5 | 5 4", - 4: "\"a\"", - 5: "\"b\"", -} - -def generate_compound_rule(ruleset:Dict[int, str], compound:str) -> str: - o = "" - for x in [int(a) for a in compound.split(" ")]: - o += generate_rule(ruleset, rule=x) - return o - - -memo = {} - - -def generate_rule(ruleset:Dict[int, str], rule:int=0) -> str: - - if rule in memo: - return memo[rule] - - output = "" - - rule_content = ruleset[rule] - - r = char_regex.match(rule_content) - if r: - # is a single character - return r.group(1) - - # if we get here, that means there are multiple options for the rule - # hence we need to open a new set of brackets - output += "(" - - r = or_rule_regex.match(rule_content) - if r: - # rule is an OR rule - output += generate_compound_rule(ruleset, r.group(1)) - output += "|" - output += generate_compound_rule(ruleset, r.group(3)) - - r = single_rule_regex.match(rule_content) - if r: - # rule is a simple sequence rule - output += generate_compound_rule(ruleset, r.group(1)) - - # close original set of brackets - output += ")" - - memo[rule] = output - - return output - - -rules, _ = open("input.txt").read().strip().split("\n\n") - -rules_dict = {} -for x in rules.split("\n"): - num, definition = x.split(":") - rules_dict[int(num.strip())] = definition.strip() - -res = generate_rule(rules_dict) -print("^" + res + "$")