generated from public/aoc_template
Day 19
This commit is contained in:
parent
215b52ff63
commit
c0ffc5a8df
160
day19.py
160
day19.py
@ -1,9 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict
|
||||||
from tools.aoc import AOCDay
|
from tools.aoc import AOCDay
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from tools.math import mul
|
from tools.math import mul
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class Part:
|
class Part:
|
||||||
@ -36,9 +35,9 @@ class WorkflowItem:
|
|||||||
return self.check_target
|
return self.check_target
|
||||||
else:
|
else:
|
||||||
match self.check_comp:
|
match self.check_comp:
|
||||||
case '<':
|
case "<":
|
||||||
result = part.attr[self.check_attr] < self.check_value
|
result = part.attr[self.check_attr] < self.check_value
|
||||||
case '>':
|
case ">":
|
||||||
result = part.attr[self.check_attr] > self.check_value
|
result = part.attr[self.check_attr] > self.check_value
|
||||||
case _:
|
case _:
|
||||||
assert False, "unhandled comparator: %s" % self.check_comp
|
assert False, "unhandled comparator: %s" % self.check_comp
|
||||||
@ -46,11 +45,14 @@ class WorkflowItem:
|
|||||||
if result:
|
if result:
|
||||||
return self.check_target
|
return self.check_target
|
||||||
|
|
||||||
def __str__(self):
|
def reverse(self) -> WorkflowItem:
|
||||||
return f"{self.check_attr} {self.check_comp} {self.check_value} ? => {self.check_target}"
|
if self.check_attr is None:
|
||||||
|
return self
|
||||||
def __repr__(self):
|
else:
|
||||||
return str(self)
|
if self.check_comp == ">":
|
||||||
|
return WorkflowItem(f"{self.check_attr}<{self.check_value + 1}:{self.check_target}")
|
||||||
|
else:
|
||||||
|
return WorkflowItem(f"{self.check_attr}>{self.check_value - 1}:{self.check_target}")
|
||||||
|
|
||||||
|
|
||||||
class Workflow:
|
class Workflow:
|
||||||
@ -69,118 +71,50 @@ class Workflow:
|
|||||||
def has_target(self, target: str) -> bool:
|
def has_target(self, target: str) -> bool:
|
||||||
return target in [x.check_target for x in self.flow]
|
return target in [x.check_target for x in self.flow]
|
||||||
|
|
||||||
|
def reached_workflow(self, target: str) -> list[list[WorkflowItem]]:
|
||||||
def get_combinations(workflow: Workflow, target: str, attr_dict: dict[str, dict[str, int]] = None) -> int:
|
filter_lists = []
|
||||||
if workflow is None:
|
my_list = []
|
||||||
return 0
|
for item in self.flow:
|
||||||
|
if item.check_target != target:
|
||||||
if attr_dict is None:
|
my_list.append(item.reverse())
|
||||||
attr_dict = {
|
|
||||||
'x': {'>': 0, '<': 4001},
|
|
||||||
'm': {'>': 0, '<': 4001},
|
|
||||||
'a': {'>': 0, '<': 4001},
|
|
||||||
's': {'>': 0, '<': 4001},
|
|
||||||
}
|
|
||||||
|
|
||||||
#print(f"check flow {workflow.name}")
|
|
||||||
found_target = False
|
|
||||||
for item in reversed(workflow.flow):
|
|
||||||
if item.check_target != target and not found_target:
|
|
||||||
#print(f" - ignore item {item.check_target}")
|
|
||||||
continue
|
|
||||||
if item.check_target == target and not found_target:
|
|
||||||
# handle: lkl{x>3811:A,m<1766:R,x>3645:A,R}
|
|
||||||
# handle: qfk{x<2170:A,s<3790:R,m<2700:xzk,A}
|
|
||||||
print(f" - found target {item.check_target}, {item.check_attr}, {item.check_comp}, {item.check_value}")
|
|
||||||
if item.check_attr is not None:
|
|
||||||
if item.check_comp == '>':
|
|
||||||
attr_dict[item.check_attr]['>'] = max(attr_dict[item.check_attr]['>'], item.check_value)
|
|
||||||
else:
|
else:
|
||||||
attr_dict[item.check_attr]['<'] = min(attr_dict[item.check_attr]['<'], item.check_value)
|
if item.check_attr is None:
|
||||||
found_target = True
|
filter_lists.append(my_list)
|
||||||
else:
|
else:
|
||||||
#print(f" - found must fail condition {item.check_target}, {item.check_attr}, {item.check_comp}, {item.check_value}")
|
filter_lists.append(my_list + [item])
|
||||||
if item.check_attr is not None and item.check_target != target:
|
my_list.append(item.reverse())
|
||||||
if item.check_comp == '>':
|
|
||||||
attr_dict[item.check_attr]['<'] = min(attr_dict[item.check_attr]['<'], item.check_value + 1)
|
|
||||||
else:
|
|
||||||
attr_dict[item.check_attr]['>'] = max(attr_dict[item.check_attr]['>'], item.check_value - 1)
|
|
||||||
|
|
||||||
if not found_target:
|
if self.source is not None:
|
||||||
return 0
|
new_filter_lists = []
|
||||||
|
sub_lists = self.source.reached_workflow(self.name)
|
||||||
|
for sub_list in sub_lists:
|
||||||
|
for my_list in filter_lists:
|
||||||
|
new_filter_lists.append(sub_list + my_list)
|
||||||
|
|
||||||
#print(f"{workflow.name}, {attr_dict=}")
|
filter_lists = new_filter_lists
|
||||||
if workflow.name != 'in' and workflow.source is not None:
|
|
||||||
#print(f"recurse to {workflow.source.name}")
|
|
||||||
return get_combinations(workflow.source, workflow.name, attr_dict)
|
|
||||||
|
|
||||||
f = mul([v['<'] - v['>'] - 1 for v in attr_dict.values()])
|
return filter_lists
|
||||||
#print("final", workflow.name, attr_dict, "=>", f)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def get_unfiltered_parts(filters: list[WorkflowItem]) -> int:
|
def get_unfiltered_parts(filters: list[WorkflowItem]) -> int:
|
||||||
attr_dict = {
|
attr_dict = {
|
||||||
'x': {'>': 0, '<': 4001},
|
"x": {">": 0, "<": 4001},
|
||||||
'm': {'>': 0, '<': 4001},
|
"m": {">": 0, "<": 4001},
|
||||||
'a': {'>': 0, '<': 4001},
|
"a": {">": 0, "<": 4001},
|
||||||
's': {'>': 0, '<': 4001},
|
"s": {">": 0, "<": 4001},
|
||||||
}
|
}
|
||||||
for item in filters:
|
for item in filters:
|
||||||
match item.check_comp:
|
match item.check_comp:
|
||||||
case '>':
|
case ">":
|
||||||
attr_dict[item.check_attr]['>'] = max(attr_dict[item.check_attr]['>'], item.check_value)
|
attr_dict[item.check_attr][">"] = max(attr_dict[item.check_attr][">"], item.check_value)
|
||||||
case '<':
|
case "<":
|
||||||
attr_dict[item.check_attr]['<'] = min(attr_dict[item.check_attr]['<'], item.check_value)
|
attr_dict[item.check_attr]["<"] = min(attr_dict[item.check_attr]["<"], item.check_value)
|
||||||
case _:
|
case _:
|
||||||
assert False, f"unexpected check comp {item.check_comp}, {item.check_target}"
|
assert False, f"unexpected check comp {item.check_comp}, {item.check_target}"
|
||||||
|
|
||||||
return mul([v['<'] - v['>'] - 1 for v in attr_dict.values()])
|
return mul([v["<"] - v[">"] - 1 for v in attr_dict.values()])
|
||||||
|
|
||||||
|
|
||||||
def get_foo(workflows: dict[str, Workflow]) -> int:
|
|
||||||
filters = defaultdict(list)
|
|
||||||
a_count, r_count = 0, 0
|
|
||||||
q = deque()
|
|
||||||
q.append('in')
|
|
||||||
while q:
|
|
||||||
workflow_name = q.popleft()
|
|
||||||
print("check workflow", workflow_name)
|
|
||||||
workflow = workflows[workflow_name]
|
|
||||||
for item in workflow.flow:
|
|
||||||
print(f" - check item {item} with filters {filters[workflow_name]}")
|
|
||||||
reached_me = get_unfiltered_parts(filters[workflow_name])
|
|
||||||
print(" - before item, got reached by ", reached_me)
|
|
||||||
if item.check_attr is None:
|
|
||||||
if item.check_target == 'A':
|
|
||||||
a_count += reached_me
|
|
||||||
elif item.check_target == 'R':
|
|
||||||
r_count += reached_me
|
|
||||||
else:
|
|
||||||
filters[item.check_target].extend(filters[workflow_name])
|
|
||||||
q.append(item.check_target)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
filters[workflow_name].append(item)
|
|
||||||
survived_filter = get_unfiltered_parts(filters[workflow_name])
|
|
||||||
filter_diff = reached_me - survived_filter
|
|
||||||
print(" - after item, got reached by ", filter_diff)
|
|
||||||
if item.check_target == 'A':
|
|
||||||
a_count += filter_diff
|
|
||||||
elif item.check_target == 'R':
|
|
||||||
r_count += filter_diff
|
|
||||||
else:
|
|
||||||
if item.check_comp is not None:
|
|
||||||
filters[item.check_target].append(item)
|
|
||||||
q.append(item.check_target)
|
|
||||||
|
|
||||||
assert a_count + r_count == 4000**4, f"{a_count=} and {r_count=}"
|
|
||||||
return a_count
|
|
||||||
|
|
||||||
# 361661634528000
|
|
||||||
# 167409079868000
|
|
||||||
# 256000000000000
|
|
||||||
|
|
||||||
class Day(AOCDay):
|
class Day(AOCDay):
|
||||||
inputs = [
|
inputs = [
|
||||||
[
|
[
|
||||||
@ -189,8 +123,8 @@ class Day(AOCDay):
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
(167409079868000, "input19_test"),
|
(167409079868000, "input19_test"),
|
||||||
(None, "input19"),
|
(132392981697081, "input19"),
|
||||||
]
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
def parse_input(self) -> (dict[str, Workflow], list[Part]):
|
def parse_input(self) -> (dict[str, Workflow], list[Part]):
|
||||||
@ -213,24 +147,24 @@ class Day(AOCDay):
|
|||||||
ans = 0
|
ans = 0
|
||||||
for part in parts:
|
for part in parts:
|
||||||
target = workflows["in"].get_target(part)
|
target = workflows["in"].get_target(part)
|
||||||
while target not in ['A', 'R']:
|
while target not in ["A", "R"]:
|
||||||
target = workflows[target].get_target(part)
|
target = workflows[target].get_target(part)
|
||||||
|
|
||||||
if target == 'A':
|
if target == "A":
|
||||||
ans += part.get_value()
|
ans += part.get_value()
|
||||||
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def part2(self) -> Any:
|
def part2(self) -> Any:
|
||||||
workflows, _ = self.parse_input()
|
workflows, _ = self.parse_input()
|
||||||
return get_foo(workflows)
|
|
||||||
ans = 0
|
ans = 0
|
||||||
for workflow in workflows.values():
|
for workflow in workflows.values():
|
||||||
if workflow.has_target('A'):
|
if workflow.has_target("A"):
|
||||||
ans += get_combinations(workflow, 'A')
|
filters = workflow.reached_workflow("A")
|
||||||
|
ans += sum(get_unfiltered_parts(x) for x in filters)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
day = Day(2023, 19)
|
day = Day(2023, 19)
|
||||||
day.run(verbose=True)
|
day.run(verbose=True)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user