112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
|
#!/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}")
|
||
|
|
||
|
|
||
|
|