Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


2020-12-19 - Monster Messages

(original .ipynb)

Day 19 input is a sequence of rules followed by a sequence of messages strings (mine is here). The rules are like a parse tree and determine whether a message is valid or not. Part 1 involves finding the number of valid messages. Part 2 involves modifying rule 8 and 11 and then finding the number of valid messages again.

Note: I started off with a very neat little parser - but it was stupid and returned the first valid sequence. This was OK for part one, but with the loops it unfortunately returned too early. I then hacked on it for a bit, went out for a walk with alfie, hacked a bit more, made french toast for breakfast and then hacked until it worked. So it's awful but I'm not coming back to sort that. Life's too short :-)

puzzle_input_str = open("puzzle_input/day19.txt", "r").read()

test_input_str = """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"""

def parse_rule(input_line):
    rule_parts = input_line.strip().split(": ")
    
    if len(rule_parts) == 2:
        index, rule_str = rule_parts
        
        if "\"" in rule_str:
            return int(index), rule_str.removeprefix("\"").removesuffix("\"")
        
        rule_str_parts = rule_str.split(" | ")

        return int(index), [ 
            [int(d) for d in rule_sequence.split(" ")]
            for rule_sequence
            in rule_str_parts
        ]

    # or we've got a blank line - signalling the end of the rules
    return None

def parse_input(input_str):
    rules = {}
    messages = []
    parsing_rules = True
    for input_line in input_str.split("\n"):
        if parsing_rules:
            rule_parts = parse_rule(input_line)
            if not rule_parts:
                parsing_rules = False
            else:
                index, rule = rule_parts
                rules[index] = rule
        else:
            messages.append(input_line)            
    
    return rules, messages

def parse_sequence(message, sequence, rules):
    if len(sequence) == 0:
        return [[True, message]]
    
    rule = sequence[0]
    results = [m for r,m in parse_message(message, rule, rules) if r]
    
    if len(results) ==  0:
        return [[False, None]]
    
    sequence_results = [parse_sequence(m, sequence[1:], rules) for m in results]

    flat_list = []
    for sublist in sequence_results:
        for r, m in sublist:
            if r:
                flat_list.append([r,m])
            
    if len(flat_list) == 0:
        return [[False, None]]
    return flat_list

def parse_message(message, current_rule_index, rules):
    current_rule = rules[current_rule_index]

    if len(message) == 0:
        return [[False, None]]
    
    if type(current_rule) is str:
        if len(message) == 0:
            return [[False, None]]
        elif message[0] == current_rule:
            return [[True, message[1:]]]
        else:
            return [[False, None]]
    else:
        results = []
        for rule_sequence in current_rule:
            results += parse_sequence(message, rule_sequence, rules)            
        
        return results

    return [[False, message]]

def solve(input_str, adjustments_str=""):
    rules, messages = parse_input(input_str)
    
    adjustments = [parse_rule(rule_adj) for rule_adj in adjustments_str.split("\n")] if adjustments_str else []
    for idx, adjustment in adjustments:
        rules[idx] = adjustment
    
    results = [parse_message(message, 0, rules) for message in messages]
    
    c = 0
    for res in results:
        result, remainder = res[0]
        if result and remainder == "":
            c += 1
        
    return c

assert 2 == solve(test_input_str)
solve(puzzle_input_str)
144
test_input_str_2 = """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

abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa
bbabbbbaabaabba
babbbbaabbbbbabbbbbbaabaaabaaa
aaabbbbbbaaaabaababaabababbabaaabbababababaaa
bbbbbbbaaaabbbbaaabbabaaa
bbbababbbbaaaaaaaabbababaaababaabab
ababaaaaaabaaab
ababaaaaabbbaba
baabbaaaabbaaaababbaababb
abbbbabbbbaaaababbbbbbaaaababb
aaaaabbaabaaaaababaa
aaaabbaaaabbaaa
aaaabbaabbaaaaaaabbbabbbaaabbaabaaa
babaaabbbaaabaababbaabababaaab
aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba"""

rule_adjustments_str = """8: 42 | 42 8
11: 42 31 | 42 11 31"""

assert 3 == solve(test_input_str_2)
assert 12 == solve(test_input_str_2, rule_adjustments_str)

print(solve(puzzle_input_str, rule_adjustments_str))
260