The last few days were brutal, so I’ve resorted to posting out of sequence as I complete them.
Today’s puzzle, as ever, starts unassumingly. For part 1, you are given a number of records like this:
{x=787,m=2655,a=1222,s=2876}
You also receive a table of rules, each of which assigns a record to a new rule, accepts it (A
), or rejects it (R
) based on application of Boolean tests:
px{a<2006:qkq,m>2090:A,rfg}
Writing a program to solve part 1 is easy enough. Part 2, as you should have learned to expect by now, massively increases the search space. Now, each field can take any value from 1 to 4000 inclusive, and you have to compute how many of the 4000^4
combinations are valid according to the rules.
Brute force, since this is past day 15, is hopeless. Instead, you can use depth-first search to follow each possible path through the rules and record each valid combination of ranges. That leaves the problem of aggregating them. I struggled with this for awhile before I found a hint on the subreddit that each rule partitioned the space of acceptable intervals, so all the valid states were disjoint. That meant I could just compute the combinations for each record and add them together.
def dfs(workflows):
= {f: Interval(1, 4000) for f in FIELDS}
start = []
valid
def inner(state, workflow):
= dict(state)
state = workflows[workflow]
workflow
for rule in workflow:
if type(rule) == dict:
= rule["field"]
field # Remember < x means x-1 is max value
= state[field].narrow(rule["value"], rule["operator"])
new if new.valid:
= state | ({field: new})
new_state = rule["target"]
target # If invalid, reject here
if target == "A":
valid.append(new_state)elif target != "R":
inner(new_state, target)
# Exclude any state caught by rule
if rule["operator"] == ">":
= Interval(state[field].low, rule["value"])
state[field] else:
# < case
= Interval(rule["value"], state[field].high)
state[field]
if not state[field].valid:
return
# Catchall rule, so break
elif rule == "A":
valid.append(state)return
elif rule == "R":
return
else:
inner(state, rule)
"in")
inner(start, return sum(prod(r.high - r.low + 1 for r in v.values()) for v in valid)