382 lines
9.6 KiB
Python
Executable File
382 lines
9.6 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
from itertools import product
|
|
from typing import Dict
|
|
from typing import List
|
|
from typing import Optional
|
|
from typing import Tuple
|
|
|
|
MAX_DEPTH = 10
|
|
|
|
|
|
class Rule(object):
|
|
_rule_body: str
|
|
key: int
|
|
_values: List[str]
|
|
_depth: int
|
|
|
|
def __init__(self, key: int, rule_body: str):
|
|
self.key = key
|
|
self._rule_body = rule_body
|
|
self._values = []
|
|
self._depth = 0
|
|
|
|
def validate(self, s: str) -> bool:
|
|
return s in self.values()
|
|
|
|
def values(self) -> List[str]:
|
|
if not self._values:
|
|
if self._depth >= MAX_DEPTH:
|
|
# Break out of recursion
|
|
return []
|
|
|
|
self._depth += 1
|
|
|
|
values: List[str] = []
|
|
for sub_rule in self._rule_body.split(" | "):
|
|
rule_combo: "Rule" = ValueRule(-1, [""])
|
|
for rule_key in sub_rule.split(" "):
|
|
rule_combo += RULE_INDEX[int(rule_key)]
|
|
|
|
values += rule_combo.values()
|
|
|
|
self._values = values
|
|
|
|
return self._values
|
|
|
|
def __add__(self, other) -> "Rule":
|
|
values = [
|
|
"".join(v)
|
|
for v in product(self.values(), other.values())
|
|
]
|
|
return ValueRule(
|
|
-1,
|
|
values,
|
|
)
|
|
|
|
def __mul__(self, other) -> List[str]:
|
|
raise NotImplementedError()
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Rule key={self.key} rule={self._rule_body}>"
|
|
|
|
|
|
class ValueRule(Rule):
|
|
key: int
|
|
|
|
def __init__(self, key: int, values: List[str]):
|
|
self.key = key
|
|
self._values = values
|
|
|
|
def values(self) -> List[str]:
|
|
return self._values
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<ValueRule key={self.key} values={self.values()}>"
|
|
|
|
|
|
def parse_rule(line) -> Tuple[int, Rule]:
|
|
rule_index = line.find(":")
|
|
if rule_index <= 0:
|
|
raise ValueError(f"Could not find rule number in {line}")
|
|
rule_key = int(line[:rule_index])
|
|
rule_body = line[rule_index+2:]
|
|
rule: Optional[Rule] = None
|
|
if rule_body[0] == '"':
|
|
rule = ValueRule(rule_key, [rule_body[1:-1]])
|
|
else:
|
|
rule = Rule(rule_key, rule_body)
|
|
|
|
return rule_key, rule
|
|
|
|
|
|
RULE_INDEX: Dict[int, Rule] = {}
|
|
|
|
|
|
def test():
|
|
test_input = (
|
|
"0: 4 1 5",
|
|
"1: 2 3 | 3 2",
|
|
"2: 4 4 | 5 5",
|
|
"3: 4 5 | 5 4",
|
|
'4: "a"',
|
|
'5: "b"',
|
|
)
|
|
RULE_INDEX.update({
|
|
parse_rule(line)
|
|
for line in test_input
|
|
})
|
|
|
|
print(RULE_INDEX)
|
|
|
|
for rule in RULE_INDEX.values():
|
|
print(rule.key, rule.values())
|
|
|
|
test_eval = (
|
|
"ababbb",
|
|
"bababa",
|
|
"abbbab",
|
|
"aaabbb",
|
|
"aaaabbb",
|
|
)
|
|
|
|
rule_0 = RULE_INDEX[0]
|
|
matched = 0
|
|
|
|
for t in test_eval:
|
|
if rule_0.validate(t):
|
|
matched += 1
|
|
|
|
print(f"Total matched is {matched}")
|
|
|
|
|
|
def part1():
|
|
rules_parsed = False
|
|
matched = 0
|
|
|
|
with open("input.txt") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
print(line, end="")
|
|
if line == "":
|
|
rules_parsed = True
|
|
print("DONE PARSING RULES")
|
|
elif not rules_parsed:
|
|
key, rule = parse_rule(line)
|
|
RULE_INDEX[key] = rule
|
|
print(" Parsed")
|
|
elif RULE_INDEX[0].validate(line):
|
|
matched += 1
|
|
print(" OK")
|
|
else:
|
|
print(" Not OK")
|
|
|
|
print(f"Total matched is {matched}")
|
|
|
|
|
|
def chunk(a, n):
|
|
return (a[i:i+n] for i in range(0, len(a), n))
|
|
|
|
|
|
def count_prefix(value: str, prefix: str) -> int:
|
|
count = 0
|
|
for c in chunk(value, len(prefix)):
|
|
if c == prefix:
|
|
count += 1
|
|
else:
|
|
break
|
|
|
|
return count
|
|
|
|
|
|
class Rule0(Rule):
|
|
def __init__(self):
|
|
super().__init__(0, "")
|
|
|
|
def validate(self, value: str) -> bool:
|
|
r42 = RULE_INDEX[42]
|
|
r31 = RULE_INDEX[31]
|
|
|
|
# print(42, "\n".join(r42.values()))
|
|
# print(31, "\n".join(r31.values()))
|
|
|
|
size = None
|
|
for v in r42.values():
|
|
if size is None:
|
|
size = len(v)
|
|
assert len(v) == size
|
|
for v in r31.values():
|
|
assert len(v) == size
|
|
|
|
match42, match31 = 0, 0
|
|
for c in chunk(value, size):
|
|
if match31 == 0 and r42.validate(c):
|
|
match42 += 1
|
|
elif match42 != 0 and r31.validate(c):
|
|
match31 += 1
|
|
else:
|
|
return False
|
|
|
|
# if value == "abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa":
|
|
# from pdb import set_trace; set_trace()
|
|
|
|
if match42 > match31 and match31 > 0:
|
|
return True
|
|
|
|
return False
|
|
|
|
# if RULE_INDEX[0].validate(value):
|
|
# from pdb import set_trace; set_trace()
|
|
|
|
# Check beginning starts with "8"
|
|
for prefix1 in r42.values():
|
|
prefix_count = count_prefix(value, prefix1)
|
|
|
|
if prefix_count == 0:
|
|
# Not even one chunk was matched, this is a bust
|
|
continue
|
|
|
|
# Trim first prefix out
|
|
back_half = value[len(prefix1) * prefix_count:]
|
|
|
|
# Check following "11" match
|
|
for prefix2, suffix in product(r42.values(), r31.values()):
|
|
remain = back_half
|
|
|
|
if RULE_INDEX[0].validate(value) and prefix1 == "bbabb" and prefix2 == "bbaab" and suffix == "aabba":
|
|
from pdb import set_trace; set_trace()
|
|
|
|
# If prefixes aren't the same, look for the new one
|
|
if prefix1 != prefix2:
|
|
prefix_count = count_prefix(remain, prefix2)
|
|
# Trim the new prefix
|
|
remain = remain[len(prefix2) * prefix_count:]
|
|
|
|
if prefix_count == 0:
|
|
# No valid prefix
|
|
continue
|
|
|
|
suffix_count = count_prefix(remain, suffix)
|
|
|
|
if suffix_count == 0:
|
|
# No valid suffix
|
|
continue
|
|
|
|
# Trim the suffix
|
|
remain = remain[len(suffix) * suffix_count:]
|
|
if len(remain) > 0:
|
|
# There is something after the suffix
|
|
continue
|
|
|
|
# Make sure we've seen at least the same number of prefixes
|
|
if prefix_count >= suffix_count:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def part2():
|
|
# Update two changed rules
|
|
# 0: 8 11
|
|
|
|
# Test short circuits
|
|
# RULE_INDEX[42] = parse_rule('42: "a"')[1]
|
|
# RULE_INDEX[31] = parse_rule('31: "b"')[1]
|
|
|
|
# rule8 = parse_rule("8: 42 | 42 8")[1]
|
|
# rule11 = parse_rule("11: 42 31 | 42 11 31")[1]
|
|
|
|
# print(RULE_INDEX[8].values())
|
|
# print(RULE_INDEX[11].values())
|
|
|
|
rules_parsed = False
|
|
matched = 0
|
|
|
|
# Empty rules
|
|
RULE_INDEX.clear()
|
|
|
|
with open("input.txt") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
print(line, end="")
|
|
if line == "":
|
|
rules_parsed = True
|
|
# All rules parsed, replace values
|
|
# RULE_INDEX[8] = rule8
|
|
# RULE_INDEX[11] = rule11
|
|
print("DONE PARSING RULES")
|
|
r42 = RULE_INDEX[42]
|
|
print("prefixes", r42.values())
|
|
r31 = RULE_INDEX[31]
|
|
print("suffixes", r31.values())
|
|
elif not rules_parsed:
|
|
key, rule = parse_rule(line)
|
|
RULE_INDEX[key] = rule
|
|
print(" Parsed")
|
|
elif Rule0().validate(line):
|
|
matched += 1
|
|
print(" OK")
|
|
else:
|
|
print(" Not OK")
|
|
|
|
print(f"Total matched is {matched}")
|
|
|
|
|
|
def test2():
|
|
r0 = Rule0()
|
|
# RULE_INDEX[42] = ValueRule(42, ["prefixa", "prefixb"])
|
|
# RULE_INDEX[31] = ValueRule(31, ["sa", "sb"])
|
|
|
|
test_input = (
|
|
"42: 9 14 | 10 1",
|
|
"9: 14 27 | 1 26",
|
|
"10: 23 14 | 28 1",
|
|
'1: "a"',
|
|
"11: 42 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",
|
|
"26: 14 22 | 1 20",
|
|
"18: 15 15",
|
|
"7: 14 5 | 1 21",
|
|
"24: 14 1",
|
|
)
|
|
RULE_INDEX.update({
|
|
parse_rule(line)
|
|
for line in test_input
|
|
})
|
|
|
|
og_valid = 0
|
|
new_valid = 0
|
|
|
|
for v in (
|
|
"abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa",
|
|
"bbabbbbaabaabba",
|
|
"babbbbaabbbbbabbbbbbaabaaabaaa",
|
|
"aaabbbbbbaaaabaababaabababbabaaabbababababaaa",
|
|
"bbbbbbbaaaabbbbaaabbabaaa",
|
|
"bbbababbbbaaaaaaaabbababaaababaabab",
|
|
"ababaaaaaabaaab",
|
|
"ababaaaaabbbaba",
|
|
"baabbaaaabbaaaababbaababb",
|
|
"abbbbabbbbaaaababbbbbbaaaababb",
|
|
"aaaaabbaabaaaaababaa",
|
|
"aaaabbaaaabbaaa",
|
|
"aaaabbaabbaaaaaaabbbabbbaaabbaabaaa",
|
|
"babaaabbbaaabaababbaabababaaab",
|
|
"aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba",
|
|
):
|
|
og = RULE_INDEX[0].validate(v)
|
|
n = r0.validate(v)
|
|
print(v, "og", og, "new", n)
|
|
if og:
|
|
og_valid += 1
|
|
if n:
|
|
new_valid += 1
|
|
|
|
print("og valid:", og_valid, "new valid:", new_valid)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# part1()
|
|
part2()
|
|
# test2()
|