Ryan is an engineer in the Sacramento Area with a focus in Python, Ruby, and Rust. Bash/Python Exercism mentor. Coding, physics, calculus, music, woodworking. Looking for work!
It's not pretty. It's not elegant. I'm sure there's some Regex theory that would have made this puzzle easier than it was. But I got it done, and that's all that counts. Hand-decoded Rules 0, 8, and 11 to decouple the infinite looping from the rule resolution algorithm, so I ended up being able to reuse most of my work from part 1. :) Don't look at the code for part 2. Just don't.
fromitertoolsimportproduct,chain,takewhileclassRule:"""A Rule consists of all possible combinations of characters that
match it.
Properties:
name (str): the ID of the rule, a positive integer as a string
rules (List[List[str]]): a list of the initial raw rules that
are available to match. Each rule is a list of ID's pointing
to another rule or a raw character
resolved_values (Set[str]): concrete strings that this rule matches
"""def__init__(self,text):name,_colon,tail=text.partition(": ")self.name=nameif'"'intail:self.resolved_values={tail.strip('"')}returnparts=tail.split(" | ")self.rules=[part.split()forpartinparts]self.resolved_values=Nonedefresolve(self,rulebook):"""A recursive function that resolves this rule and all children
rules. Caches its result in self.resolved_values.
"""ifself.resolved_values:returnself.resolved_valuesself.resolved_values={"".join(p)forruleinself.rulesforpinproduct(*[rulebook[i].resolve(rulebook)foriinrule])}returnself.resolved_valuesdefmatch(self,line):"""Returns true if this rule matches the line in one of its
resolved_values."""returnlineinself.resolved_valuesdefparse(filename):"""Parses an input file to a set of rules and a set of messages.
Input files have this format:
0: 1 4 3
1: 2 | 5
2: "a"
3: "b"
4: "c"
5: "d"
abbabacabacabacbbabbab
bababababa
baba
"""rules=dict()withopen(filename,"r")asf:while(line:=f.readline())!="\n":rule=Rule(line.strip())rules[rule.name]=rulemessages=f.read().splitlines()returnrules,messagesdefpart1(rules,messages):"""Return the number of messages that match rule 0."""returnsum(rules["0"].match(message)formessageinmessages)defchunk_message(message,size):"""Chunk a string up into `size` size chunks."""foriinrange(0,len(message),size):yieldmessage[i:i+size]defcheck_recursive(rules,message,chunksize):"""Rules 8 and 11 are recursive. Checks that the message matches
the rule '8 11'.
Rule 8: 42 | 42 8, which works out to one or more chunks matching
rule 42.
Rule 11: 42 31 | 42 11 31, which works out to one or more sets of
rule 42 followed by an equal number of sets of rule 31.
Together, these requirements mean that the message must follow the
following requirements:
1. The first and second chunk must match 42.
2. All subsequent chunks must match 42 until:
3. Chunks must then match one or more 31's.
4. We have to have matched at least one more 42 than we do 31.
It's not pretty but it does the job. TODO: finite state machine.
"""chunks=chunk_message(message,chunksize)count_42=0past_42=Falsecount_31=0forchunkinchunks:iflen(chunk)!=chunksize:# Protect against strings that aren't an even multiple of
# chunksize
returnFalseifnotpast_42andchunkinrules["42"].resolved_values:# Match one or more 42's and nothing else.
count_42+=1continuepast_42=True# Stop matching 42.
ifchunknotinrules["31"].resolved_values:# Match only 31's the rest of the way
returnFalsecount_31+=1# Ensure all requirements were met
returncount_42>=2andcount_31>=1andcount_42>=1+count_31defpart2(rules,messages):"""Modify Rule 8 and Rule 11 to be recursive. Now how many
messages match rule 0 exactly?
"""total=0len42=len(list(rules["42"].resolved_values)[0])len31=len(list(rules["31"].resolved_values)[0])assertlen42==len31,"42 chunks don't equal 31 chunks"formessageinmessages:ifcheck_recursive(rules,message,len42):total+=1returntotalif__name__=="__main__":rules,messages=parse("data/day19_test.txt")rules["0"].resolve(rules)assert2==part1(rules,messages)rules,messages=parse("data/day19_test2.txt")rules["42"].resolve(rules)rules["31"].resolve(rules)assert12==part2(rules,messages)print("All tests passed!")rules,messages=parse("data/day19.txt")rules["0"].resolve(rules)print("Part 1:",part1(rules,messages))print("Part 2:",part2(rules,messages))
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
It's not pretty. It's not elegant. I'm sure there's some Regex theory that would have made this puzzle easier than it was. But I got it done, and that's all that counts. Hand-decoded Rules 0, 8, and 11 to decouple the infinite looping from the rule resolution algorithm, so I ended up being able to reuse most of my work from part 1. :) Don't look at the code for part 2. Just don't.