This commit is contained in:
akp 2023-12-19 16:57:17 +00:00
parent 2f675b8a3c
commit 329cabb014
No known key found for this signature in database
GPG key ID: CF8D58F3DEB20755
6 changed files with 185 additions and 1 deletions

View file

@ -0,0 +1,3 @@
# [Day 19: Aplenty](https://adventofcode.com/2023/day/19)
This is not a problem I expected to be able to first-try part 2 for.

View file

@ -0,0 +1,162 @@
import sys
from collections import namedtuple
from functools import reduce
ConditionalRule = namedtuple("ConditionalRule", ["field", "op", "value", "next"])
UnconditionalRule = namedtuple("UnconditionalRule", ["next"])
Rule = ConditionalRule | UnconditionalRule
Workflows = dict[str, list[Rule]]
Part = namedtuple("Part", ["x", "m", "a", "s"])
def parse(instr: str) -> tuple[Workflows, list[Part]]:
raw_workflows, raw_parts = instr.split("\n\n")
workflows: Workflows = {}
for line in raw_workflows.splitlines():
bracket_start = line.find("{")
workflow_name = line[0:bracket_start]
rules = []
for raw_rule in line[bracket_start + 1 : -1].split(","):
colon_pos = raw_rule.find(":")
if colon_pos == -1:
rules.append(UnconditionalRule(raw_rule))
continue
field = raw_rule[0]
op = raw_rule[1]
value = int(raw_rule[2:colon_pos])
next_workflow = raw_rule[colon_pos + 1 :]
rules.append(ConditionalRule(field, op, value, next_workflow))
workflows[workflow_name] = rules
parts: list[Part] = []
for line in raw_parts.splitlines():
line = line[1:-1]
sp = [x.split("=") for x in line.split(",")]
assert "".join(map(lambda x: x[0], sp)) == "xmas"
parts.append(Part(*map(lambda x: int(x[1]), sp)))
return workflows, parts
def test_rule(r: ConditionalRule, p: Part) -> bool:
test_value = p.__getattribute__(r.field)
match r.op:
case ">":
return test_value > r.value
case "<":
return test_value < r.value
case _:
raise ValueError(f"unknown operation {r.op}")
def is_acceptable(w: Workflows, p: Part) -> bool:
cursor = "in"
while not (cursor == "R" or cursor == "A"):
for rule in w[cursor]:
if (type(rule) == ConditionalRule and test_rule(rule, p)) or type(
rule
) == UnconditionalRule:
cursor = rule.next
break
return cursor == "A"
def one(instr: str):
workflows, parts = parse(instr)
acc = 0
for part in parts:
if is_acceptable(workflows, part):
acc += sum(part)
return acc
Range = tuple[int, int]
def split_range(rng: Range, rule: ConditionalRule) -> tuple[Range | None, Range | None]:
# First range is the matching one, second range is the non-matching one.
(lower, upper) = rng
match rule.op:
case "<":
if upper < rule.value:
return rng, None
if lower >= rule.value:
return None, rng
return ((lower, rule.value - 1), (rule.value, upper))
case ">":
if lower > rule.value:
return rng, None
if upper <= rule.value:
return None, rng
return ((rule.value + 1, upper), (lower, rule.value))
case _:
raise ValueError(f"unknown operation {rule.op}")
def get_acceptable_ranges(
workflows: Workflows, workflow_name: str, ranges: dict[str, Range]
) -> list[dict[str, Range]]:
if workflow_name == "A":
return [ranges]
if workflow_name == "R":
return []
res = []
for rule in workflows[workflow_name]:
if type(rule) == UnconditionalRule:
res += get_acceptable_ranges(workflows, rule.next, ranges)
continue
matches, not_matches = split_range(ranges[rule.field], rule)
if matches is not None:
x = ranges.copy()
x[rule.field] = matches
res += get_acceptable_ranges(workflows, rule.next, x)
if not_matches is not None:
ranges[rule.field] = not_matches
return res
def get_range_len(r: Range) -> int:
(start, end) = r
return (end - start) + 1
def two(instr: str):
workflows, _ = parse(instr)
acc = 0
for ranges in get_acceptable_ranges(
workflows, "in", {c: [1, 4000] for c in "xmas"}
):
acc += reduce(lambda acc, x: acc * get_range_len(x), ranges.values(), 1)
return acc
def _debug(*args, **kwargs):
kwargs["file"] = sys.stderr
print(*args, **kwargs)
if __name__ == "__main__":
if len(sys.argv) < 2 or sys.argv[1] not in ["1", "2"]:
print("Missing day argument", file=sys.stderr)
sys.exit(1)
inp = sys.stdin.read().strip()
if sys.argv[1] == "1":
print(one(inp))
else:
print(two(inp))

View file

@ -0,0 +1,14 @@
{
"1": [
{
"is": "19114",
"input": "px{a<2006:qkq,m>2090:A,rfg}\npv{a>1716:R,A}\nlnx{m>1548:A,A}\nrfg{s<537:gd,x>2440:R,A}\nqs{s>3448:A,lnx}\nqkq{x<1416:A,crn}\ncrn{x>2662:A,R}\nin{s<1351:px,qqz}\nqqz{s>2770:qs,m<1801:hdj,R}\ngd{a>3333:R,R}\nhdj{m>838:A,pv}\n\n{x=787,m=2655,a=1222,s=2876}\n{x=1679,m=44,a=2067,s=496}\n{x=2036,m=264,a=79,s=2244}\n{x=2461,m=1339,a=466,s=291}\n{x=2127,m=1623,a=2188,s=1013}\n\n"
}
],
"2": [
{
"is": "167409079868000",
"input": "px{a<2006:qkq,m>2090:A,rfg}\npv{a>1716:R,A}\nlnx{m>1548:A,A}\nrfg{s<537:gd,x>2440:R,A}\nqs{s>3448:A,lnx}\nqkq{x<1416:A,crn}\ncrn{x>2662:A,R}\nin{s<1351:px,qqz}\nqqz{s>2770:qs,m<1801:hdj,R}\ngd{a>3333:R,R}\nhdj{m>838:A,pv}\n\n{x=787,m=2655,a=1222,s=2876}\n{x=1679,m=44,a=2067,s=496}\n{x=2036,m=264,a=79,s=2244}\n{x=2461,m=1339,a=466,s=291}\n{x=2127,m=1623,a=2188,s=1013}\n\n"
}
]
}

View file

@ -31,4 +31,5 @@ A day denoted with a star means it has a visualisation.
| 15 - Lens Library | ★ ★ | Go | Still took some brainpower but this time the brainpower was needed to work out what the problem was, *not* to work out how to solve the problem. |
| 16 - The Floor Will Be Lava | ★ ★ | Python | Pathfinding, sort of, but also brute forceable?? |
| 17 - Clumsy Crucible | ★ ★ | Python | This taught me quite a lot about how to meddle with Djikstra's |
| 18 - Ladaduct Lagoon | ★ ★ | Python | Nothing quite like a problem that I thought I knew the solution to showing up my lack of mathematical knowledge. |
| 18 - Ladaduct Lagoon | ★ ★ | Python | Nothing quite like a problem that I thought I knew the solution to showing up my lack of mathematical knowledge. |
| 19 - Aplenty | ★ ★ | Python | So maybe I *can* do range maths? |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

View file

@ -33,3 +33,7 @@
{"day": 17, "part": 2, "runner": "py", "min": 26.201914072036743, "max": 26.201914072036743, "avg": 26.201914072036743, "n": 1}
{"day": 18, "part": 1, "runner": "py", "min": 0.02330160140991211, "max": 0.03203868865966797, "avg": 0.024628419876098633, "n": 100}
{"day": 18, "part": 2, "runner": "py", "min": 0.023529052734375, "max": 0.030207157135009766, "avg": 0.02483478546142578, "n": 100}
{"day": 19, "part": 1, "runner": "py", "min": 0.023938894271850586, "max": 0.05737614631652832, "avg": 0.027661228179931642, "n": 100}
{"day": 19, "part": 2, "runner": "py", "min": 0.026041030883789062, "max": 0.03458356857299805, "avg": 0.028042428493499756, "n": 100}
{"day": 19, "part": 1, "runner": "py", "min": 0.023969173431396484, "max": 0.03136777877807617, "avg": 0.026349050998687742, "n": 100}
{"day": 19, "part": 2, "runner": "py", "min": 0.024805068969726562, "max": 0.03318047523498535, "avg": 0.027637341022491456, "n": 100}