2023.19
This commit is contained in:
parent
2f675b8a3c
commit
329cabb014
6 changed files with 185 additions and 1 deletions
3
challenges/2023/19-aplenty/README.md
Normal file
3
challenges/2023/19-aplenty/README.md
Normal 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.
|
162
challenges/2023/19-aplenty/main.py
Normal file
162
challenges/2023/19-aplenty/main.py
Normal 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))
|
14
challenges/2023/19-aplenty/tests.json
Normal file
14
challenges/2023/19-aplenty/tests.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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 |
|
@ -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}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue