Day 19 (Python)

Day 19.1 test implementation

Day 19.1 (Python)

Day 19.2 (Python)

Day 19 comments and formatting (Python)

Day 19 READMEs
This commit is contained in:
akp 2020-12-19 17:21:43 +00:00
parent d3c15289ac
commit b5c226594b
No known key found for this signature in database
GPG key ID: D3E7EAA31B39637E
8 changed files with 346 additions and 1 deletions

2
.github/README.md vendored
View file

@ -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 | ![Not yet attempted][pending] | | |
| [19](/19-monsterMessages) | ![Partially complete][partial] | [Link](/19-monsterMessages/python) | |
| 20 | | | |
| 21 | | | |
| 22 | | | |

View file

@ -0,0 +1,35 @@
# [Day 19: Monster Messages](https://adventofcode.com/2020/day/19)
Regex, but this time it's not actually too much of a problem.
### Part one
The set of rules that our challenge input provides can pretty easily be parsed and converted into regular expressions, which we can use along with our language's built in regex package in order to parse and validate each message in our input.
### Part two
Part two is also done with regular expressions, however they are modified after they are generated. Since there are only two rules that have recursive properties, conditions for each are hardcoded into the script.
If the recursive rule is rule number `8`, a plus sign is added after the rule for `42`, to denote that sequence can be present one or more times.
If the recursive rule is rule number `11`, a marker is added in place of the full regular expression. When the rest of the rule has been entirely generated, rule 11 is generated alone. This is then put in the marker in the original regular expression. Since this will add another marker, this replacement is repeated 10 times.
The regular expression can then be compared against the messages as in part one.
<details><summary>Script output</summary>
```
python .\python\
AoC 2020: day 19 - Monster Messages
Python 3.8.5
Test cases
1.1 pass
2.1 pass
Answers
Part 1: 222
Part 2: 339
```
</details>

View file

@ -0,0 +1,19 @@
{
"year": "2020",
"day": "19",
"title": "Monster Messages",
"testCases": {
"one": [
{
"input": "0: 4 1 5\n1: 2 3 | 3 2\n2: 4 4 | 5 5\n3: 4 5 | 5 4\n4: \"a\"\n5: \"b\"\n\nababbb\nbababa\nabbbab\naaabbb\naaaabbb\n",
"expected": 2
}
],
"two": [
{
"input": "42: 9 14 | 10 1\n9: 14 27 | 1 26\n10: 23 14 | 28 1\n1: \"a\"\n11: 42 31\n5: 1 14 | 15 1\n19: 14 1 | 14 14\n12: 24 14 | 19 1\n16: 15 1 | 14 14\n31: 14 17 | 1 13\n6: 14 14 | 1 14\n2: 1 24 | 14 4\n0: 8 11\n13: 14 3 | 1 12\n15: 1 | 14\n17: 14 2 | 1 7\n23: 25 1 | 22 14\n28: 16 1\n4: 1 1\n20: 14 14 | 1 15\n3: 5 14 | 16 1\n27: 1 6 | 14 18\n14: \"b\"\n21: 14 1 | 1 14\n25: 1 1 | 1 14\n22: 14 14\n8: 42\n26: 14 22 | 1 20\n18: 15 15\n7: 14 5 | 1 21\n24: 14 1\n\nabbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa\nbbabbbbaabaabba\nbabbbbaabbbbbabbbbbbaabaaabaaa\naaabbbbbbaaaabaababaabababbabaaabbababababaaa\nbbbbbbbaaaabbbbaaabbabaaa\nbbbababbbbaaaaaaaabbababaaababaabab\nababaaaaaabaaab\nababaaaaabbbaba\nbaabbaaaabbaaaababbaababb\nabbbbabbbbaaaababbbbbbaaaababb\naaaaabbaabaaaaababaa\naaaabbaaaabbaaa\naaaabbaabbaaaaaaabbbabbbaaabbaabaaa\nbabaaabbbaaabaababbaabababaaab\naabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba\n",
"expected": 12
}
]
}
}

View file

@ -0,0 +1,77 @@
import json
import platform
import sys
from rich import print
from partOne import partOne
from partTwo import partTwo
def run_tests(test_cases):
do_tests = True
if len(test_cases) == 0:
do_tests = False
elif len(test_cases["one"]) == 0 and len(test_cases["two"]) == 0:
do_tests = False
if not do_tests:
print("Info: no test cases specified. Skipping tests\n")
return
print("Test cases")
def rt(tcs, f, n):
for i, tc in enumerate(tcs):
print(f"{n}.{i+1} ", end="")
expectedInt = tc["expected"]
result = f(str(tc["input"]))
if result == expectedInt:
print("[green]pass[/green]")
else:
print(f"[red]fail[/red] (got {result}, expected {expectedInt})")
rt(test_cases["one"], partOne, 1)
rt(test_cases["two"], partTwo, 2)
print()
if __name__ == "__main__":
try:
info = open("info.json").read()
except FileNotFoundError:
print("Error: could not open info.json")
sys.exit(-1)
info = json.loads(info)
year = info["year"]
day = info["day"]
title = info["title"]
print(f"[yellow]AoC {year}[/yellow]: day {day} - {title}")
print(f"Python {platform.python_version()}\n")
try:
challenge_input = open("input.txt").read()
except FileNotFoundError:
print("Error: could not open input.txt")
sys.exit(-1)
if "vis" in sys.argv:
import visualise
print("[green]Running visualisation....[/green]")
visualise.visualise(challenge_input)
sys.exit()
run_tests(info["testCases"])
if "debug" in sys.argv:
sys.exit()
print("Answers")
print("Part 1:", partOne(challenge_input))
print("Part 2:", partTwo(challenge_input))

View file

@ -0,0 +1,99 @@
from typing import Dict, List, Tuple
import re
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
replace_marker = "!!!REPLACETHISMARKER!!!"
def generate_compound_rule_regex(
ruleset: Dict[int, str], compound: str, current: int
) -> str:
# 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
o = ""
for x in [int(a) for a in compound.split(" ")]:
if x == current:
if x == 8:
o += generate_rule_regex(ruleset, 42)
o += "+"
else:
o += replace_marker
else:
o += generate_rule_regex(ruleset, x)
return o
def generate_rule_regex(ruleset: Dict[int, str], rule: int) -> str:
# 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
rule_content = ruleset[rule]
r = char_regex.match(rule_content)
if r:
# is a single character
# `116: "a"`
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
# `83: 26 131 | 47 116`
output += generate_compound_rule_regex(ruleset, r.group(1), rule)
output += "|"
output += generate_compound_rule_regex(ruleset, r.group(3), rule)
r = single_rule_regex.match(rule_content)
if r:
# rule is a simple sequence rule
# `92: 67 131`
output += generate_compound_rule_regex(ruleset, r.group(1), rule)
# close original set of brackets
output += ")"
return output
def make_ruleset_regex(ruleset: Dict[int, str]) -> str:
return "^" + generate_rule_regex(ruleset, 0) + "$"
def run(messages: List[str], regex: re.Pattern) -> int:
valid_inputs = 0
for i, message in enumerate(messages):
if regex.match(message):
valid_inputs += 1
return valid_inputs
def parse(instr: str) -> Tuple[Dict[int, str], List[str]]:
rules, messages = instr.strip().split("\n\n")
rule_dict = {}
for x in rules.split("\n"):
num, definition = x.split(":")
rule_dict[int(num.strip())] = definition.strip()
messages = messages.strip().split("\n")
return rule_dict, messages

View file

@ -0,0 +1,8 @@
from common import *
import re
def partOne(instr: str) -> int:
rules, messages = parse(instr)
rule_regex = re.compile(make_ruleset_regex(rules))
return run(messages, rule_regex)

View file

@ -0,0 +1,33 @@
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
instr = instr.replace(
"8: 42", "8: 42 | 42 8"
) # the new rule 8 can be expressed as 42 | 42+
instr = instr.replace("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 = make_ruleset_regex(rules)
eleven_regex = generate_rule_regex(rules, 11)
for _ in range(10):
rr = rr.replace(replace_marker, eleven_regex)
# run as usual
rule_regex = re.compile(rr)
return run(messages, rule_regex)

View file

@ -0,0 +1,74 @@
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 + "$")