Advertisement
illuminati229

AoC 2023 Day 19

Dec 19th, 2023
800
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.88 KB | None | 0 0
  1. from time import time
  2. import re
  3. from math import prod
  4.  
  5.  
  6. def timer_func(func):
  7.     # This function shows the execution time of
  8.     # the function object passed
  9.     def wrap_func(*args, **kwargs):
  10.         t1 = time()
  11.         result = func(*args, **kwargs)
  12.         t2 = time()
  13.         print(f'Function {func.__name__!r} executed in {(t2 - t1):.4f}s')
  14.         return result
  15.  
  16.     return wrap_func
  17.  
  18.  
  19. def rate_part(part, workflows, wfn):
  20.     if wfn in 'AR':
  21.         return wfn
  22.     cwf = workflows[wfn]
  23.     for step in cwf:
  24.         # if the step is a string, it is either accepted, rejected, or sent to another workflow
  25.         if isinstance(step, str):
  26.             # if the step is either A or R
  27.             if step in 'AR':
  28.                 # return the step
  29.                 return step
  30.             else:
  31.                 # else, run it through the workflow it says to
  32.                 return rate_part(part, workflows, step)
  33.         # otherwise we have to do the evaluation
  34.         else:
  35.             if step['op'] == '<':
  36.                 if part[step['cat']] < step['val']:
  37.                     return rate_part(part, workflows, step['dst'])
  38.             else:
  39.                 if part[step['cat']] > step['val']:
  40.                     return rate_part(part, workflows, step['dst'])
  41.  
  42.  
  43. def less_than_range(r, v):
  44.     l, u = r
  45.     if l < v <= u:
  46.         return (l, v-1), (v, u)
  47.     elif v <= l:
  48.         return None, (l, u)
  49.     elif v > u:
  50.         return (l, u), None
  51.  
  52.  
  53. def greater_than_range(r, v):
  54.     l, u = r
  55.     if l <= v < u:
  56.         return (l, v), (v + 1, u)
  57.     elif v < l:
  58.         return None, (l, u)
  59.     elif v >= u:
  60.         return (l, u), None
  61.  
  62.  
  63. def rate_part_range(part, workflows, wfn):
  64.     if wfn == 'A':
  65.         return prod([v[1] - v[0] + 1 for c, v in part.items() if c in 'xmas'])
  66.     elif wfn == 'R':
  67.         return 0
  68.     combos = 0
  69.     cwf = workflows[wfn]
  70.     for step in cwf:
  71.         # if the step is a string, it is either accepted, rejected, or sent to another workflow
  72.         if isinstance(step, str):
  73.             # if the step is either A or R
  74.             if step in 'A':
  75.                 # add the product of the ranges to the combos
  76.                 combos += prod([v[1] - v[0] + 1 for c, v in part.items() if c in 'xmas'])
  77.             else:
  78.                 # else, run it through the workflow it says to
  79.                 combos += rate_part_range(part, workflows, step)
  80.         # otherwise we have to do the evaluation
  81.         else:
  82.             if step['op'] == '<':
  83.                 # split the range into the less than and greater than
  84.                 l, u = less_than_range(part[step['cat']], step['val'])
  85.                 # lower than range gets sent to the next workflow
  86.                 if l:
  87.                     new_part = part.copy()
  88.                     new_part[step['cat']] = l
  89.                     combos += rate_part_range(new_part, workflows, step['dst'])
  90.                 # upper range remains to go through the rest of this workflow
  91.                 if u:
  92.                     part[step['cat']] = u
  93.             else:
  94.                 # split the range
  95.                 l, u = greater_than_range(part[step['cat']], step['val'])
  96.                 # lower range remains to go through the rest of this workflow
  97.                 if l:
  98.                     part[step['cat']] = l
  99.                 # upper range goes to the next workflow
  100.                 if u:
  101.                     new_part = part.copy()
  102.                     new_part[step['cat']] = u
  103.                     combos += rate_part_range(new_part, workflows, step['dst'])
  104.  
  105.     return combos
  106.  
  107.  
  108. @timer_func
  109. def day19(filepath, part2=False):
  110.     with open(filepath) as fin:
  111.         wfs, ps = fin.read().split('\n\n')
  112.  
  113.     wfl = {}
  114.     for wf in wfs.split('\n'):
  115.         name = re.search(r'(.*){', wf).groups()[0]
  116.         wfl[name] = []
  117.         for entry in re.search(r'{(.*)}', wf).groups()[0].split(','):
  118.             if ':' in entry:
  119.                 cat, op, val, dst = re.search(r'(.)([><])(\d*):([a-zA-Z]*)', entry).groups()
  120.                 wfl[name].append({'cat': cat,
  121.                                   'op': op,
  122.                                   'val': int(val),
  123.                                   'dst': dst})
  124.             else:
  125.                 wfl[name].append(entry)
  126.  
  127.     parts = [{e[0]: int(e[2:]) for e in p[1:-1].split(',')} for p in ps.split('\n')]
  128.  
  129.     if not part2:
  130.         for part in parts:
  131.             part['fd'] = rate_part(part, wfl, 'in')
  132.         return sum([sum([v for c, v in p.items() if c in 'xmas']) for p in parts if p['fd'] == 'A'])
  133.     else:
  134.         part = {i: (1, 4000) for i in 'xmas'}
  135.         return rate_part_range(part, wfl, 'in')
  136.  
  137.  
  138. def main():
  139.     assert day19('test19') == 19114
  140.     print(f"Part 1: {day19('input19')}")
  141.  
  142.     assert day19('test19', True) == 167409079868000
  143.     print(f"Part 2: {day19('input19', True)}")
  144.  
  145.  
  146. if __name__ == '__main__':
  147.     main()
  148.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement