AdventOfCode2023/19/day19.py
2023-12-19 17:11:31 +01:00

112 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
import re
from sys import exit
import concurrent.futures
FILENAME = "input.txt"
# FILENAME = "example.txt"
CATEGORIES = "xmas"
# from typing import Callable
# rules: dict[str, list[Callable[[list[int]], bool|None|str]]] = {}
rules: dict[str, list[str|bool|tuple[int,str,int,str|bool]]] = {}
def make_rulename(r): return True if r == "A" else (False if r == "R" else r)
with open(FILENAME, "r") as file:
lines = file.readlines()
empty_i = lines.index("\n")
# make rules
for i in range(0, empty_i):
curly = lines[i].find("{")
rulename = lines[i][:curly]
last_comma = lines[i].rfind(",")
catchall = make_rulename(lines[i][last_comma+1:-2])
rules[rulename] = []
for m in re.finditer(r"([xmas])([<>])(\d+):([a-zAR]+)", lines[i][curly+1:last_comma]):
assert(m is not None)
category, op, n, rule = m.groups()
# print(f"\t{CATEGORIES[category]}({category}){op}{int(n)}:{rule}")
rules[rulename].append((CATEGORIES.find(category), op, int(n), make_rulename(rule) ))
# if op == ">": rules[rulename].append(lambda cats,c=category,n=int(n),res=rule: res if cats[c] > n else None)
# else: rules[rulename].append(lambda cats,c=category,n=int(n),res=rule: res if cats[c] < n else None)
rules[rulename].append(catchall)
def apply_rules(cats: list[int], rulename) -> bool|str:
# print(f"{rulename} -> ", end="")
for rule in rules[rulename]:
if type(rule) == tuple:
c, op, n, rule = rule
if op == ">": res = rule if cats[c] > n else None
else: res = rule if cats[c] < n else None
else:
res = rule
assert(res != rulename)
if res is not None: return res
print("Error: rules ran out")
exit(1)
def is_accepted(values):
res = apply_rules(values, "in")
while type(res) != bool:
res = apply_rules(values, res)
return res
total_ratings = 0
for i in range(empty_i+1, len(lines)):
values = [ int(m.group()) for m in re.finditer(r"\d+", lines[i])]
if is_accepted(values):
total_ratings += sum(values)
print(f"Sum of ratings of accepted parts: {total_ratings}")
splits = [[1, 4001] for _ in range(4)]
for rules_list in rules.values():
for r in rules_list:
if type(r) == tuple:
c, op, n, rule = r
# splits[c].append(n)
splits[c].append(n + 1 if op == ">" else n)
# x < N
for i in range(4):
splits[i].sort()
# print(splits[i])
def get_acc_comb_count(sp:list[list[int]]):
n_accepted = 0
assert(len(sp[0]) > 1)
for x in range(0, len(sp[0])-1):
xdiff = sp[0][x+1] - sp[0][x]
# print(f"{(x * 100)//len(sp[0]):3}%, x={x}", end="\r")
for m in range(0, len(sp[1])-1):
mdiff = sp[1][m+1] - sp[1][m]
for a in range(0, len(sp[2])-1):
adiff = sp[2][a+1] - sp[2][a]
for s in range(0, len(sp[3])-1):
sdiff = sp[3][s+1] - sp[3][s]
v = [sp[0][x], sp[1][m], sp[2][a], sp[3][s]]
if is_accepted(v):
n_accepted += xdiff * mdiff * adiff * sdiff
return n_accepted
def split_list(l, n):
new_lists = []
i = 1
n = max(len(l) // n, 1)
while i < len(l):
new_lists.append(l[i-1:min(i+n, len(l))])
i += n
return new_lists
n_threads = 16
with concurrent.futures.ThreadPoolExecutor(max_workers=n_threads) as executor:
it = executor.map(get_acc_comb_count, ([s, splits[1], splits[2], splits[3]] for s in split_list(splits[0], n_threads)))
n_accepted = 0
for n in it:
n_accepted += n
print(f"Number of accepted combinations: {n_accepted}")