Skip to content

Commit f58bb25

Browse files
authored
Add 2023/19 py
1 parent 413df9e commit f58bb25

File tree

8 files changed

+214
-0
lines changed

8 files changed

+214
-0
lines changed

2023/19/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Advent of Code - 2023 Day 19
2+
Here are my solutions to this puzzle.
3+
4+
* Problem instructions: [adventofcode.com/2023/day/19](https://adventofcode.com/2023/day/19)
5+
* Input: [adventofcode.com/2023/day/19/input](https://adventofcode.com/2023/day/19/input)
6+
7+
Fetch input by exporting `$AOC_SESSION` in your shell and then:
8+
```bash
9+
curl -OJLsb session=$AOC_SESSION adventofcode.com/2023/day/19/input
10+
```
11+
12+
or run `fetch_input.sh` in this directory.

2023/19/fetch_input.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env sh
2+
# Make sure $AOC_SESSION is exported before running this script.
3+
4+
curl --remote-name --remote-header-name --silent --fail -A 'https://erikw.me/contact' --cookie "session=$AOC_SESSION" "https://adventofcode.com/2023/day/19/input"
5+
test "$?" -eq 0 && echo "Fetched input" && exit 0 || echo "Failed to fetch input" && exit 1

2023/19/input1.0

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
px{a<2006:qkq,m>2090:A,rfg}
2+
pv{a>1716:R,A}
3+
lnx{m>1548:A,A}
4+
rfg{s<537:gd,x>2440:R,A}
5+
qs{s>3448:A,lnx}
6+
qkq{x<1416:A,crn}
7+
crn{x>2662:A,R}
8+
in{s<1351:px,qqz}
9+
qqz{s>2770:qs,m<1801:hdj,R}
10+
gd{a>3333:R,R}
11+
hdj{m>838:A,pv}
12+
13+
{x=787,m=2655,a=1222,s=2876}
14+
{x=1679,m=44,a=2067,s=496}
15+
{x=2036,m=264,a=79,s=2244}
16+
{x=2461,m=1339,a=466,s=291}
17+
{x=2127,m=1623,a=2188,s=1013}

2023/19/instructions.url

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[InternetShortcut]
2+
URL = https://adventofcode.com/2023/day/19

2023/19/output1.0

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
19114

2023/19/output2.0

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
167409079868000

2023/19/part1.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
import sys
3+
import operator
4+
from pprint import pprint
5+
6+
7+
WORKFLOW_START = "in"
8+
WORKFLOW_ACCEPT = "A"
9+
WORKFLOW_REJECT = "R"
10+
11+
OP_FUNCS = {
12+
"<" : operator.lt,
13+
">" : operator.gt,
14+
}
15+
16+
def read_input():
17+
with open(sys.argv[1]) as file:
18+
workflow_lines, rating_lines = file.read().split("\n\n")
19+
20+
workflows = {}
21+
for workflow_line in workflow_lines.split("\n"):
22+
name, rules = workflow_line.split("{")
23+
workflows[name] = []
24+
for rule in rules[:-1].split(","):
25+
if ":" in rule:
26+
condition, target = rule.split(":")
27+
category, cmp, value = condition[0], condition[1], int(condition[2:])
28+
workflows[name].append((category, cmp, value, target))
29+
else:
30+
workflows[name].append((rule,))
31+
32+
ratings = []
33+
for rating_line in rating_lines.strip().split("\n"):
34+
# ratings.append({k:v for k, v in zip(["x", "m", "a", "s",], re.findall(r"\d+", rating_line))})
35+
ratings.append({})
36+
for rating in rating_line.strip("{}").split(","):
37+
category, value = rating.split("=")
38+
ratings[-1][category] = int(value)
39+
40+
return workflows, ratings
41+
42+
43+
def process_parts(workflows, ratings):
44+
accepted = []
45+
for rating in ratings:
46+
workflow = WORKFLOW_START
47+
while workflow not in [WORKFLOW_ACCEPT, WORKFLOW_REJECT]:
48+
for step in workflows[workflow]:
49+
if len(step) == 1:
50+
workflow = step[0]
51+
else:
52+
category, cmp, value, target = step
53+
if OP_FUNCS[cmp](rating[category], value):
54+
workflow = target
55+
break
56+
if workflow == WORKFLOW_ACCEPT:
57+
accepted.append(rating)
58+
return accepted
59+
60+
61+
62+
def main():
63+
workflows, ratings = read_input()
64+
accepted = process_parts(workflows, ratings)
65+
66+
rating_sum = sum([sum(a.values()) for a in accepted])
67+
print(rating_sum)
68+
69+
if __name__ == '__main__':
70+
main()

2023/19/part2.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python3
2+
import sys
3+
import operator
4+
from pprint import pprint
5+
import copy
6+
from math import prod
7+
8+
9+
RANGE_MIN = 1
10+
RANGE_MAX = 4000
11+
12+
WORKFLOW_START = "in"
13+
WORKFLOW_ACCEPT = "A"
14+
WORKFLOW_REJECT = "R"
15+
16+
OP_FUNCS = {
17+
"<" : operator.lt,
18+
">" : operator.gt,
19+
}
20+
21+
def read_input():
22+
with open(sys.argv[1]) as file:
23+
workflow_lines, rating_lines = file.read().split("\n\n")
24+
25+
# Add Accpet/Reject as own workflows to make recursion easier.
26+
workflows = {
27+
WORKFLOW_ACCEPT: (WORKFLOW_ACCEPT,),
28+
WORKFLOW_REJECT: (WORKFLOW_REJECT,),
29+
}
30+
for workflow_line in workflow_lines.split("\n"):
31+
name, rules = workflow_line.split("{")
32+
workflows[name] = []
33+
for rule in rules[:-1].split(","):
34+
if ":" in rule:
35+
condition, target = rule.split(":")
36+
category, cmp, value = condition[0], condition[1], int(condition[2:])
37+
workflows[name].append((category, cmp, value, target))
38+
else:
39+
workflows[name].append((rule,))
40+
41+
ratings = []
42+
for rating_line in rating_lines.strip().split("\n"):
43+
# ratings.append({k:v for k, v in zip(["x", "m", "a", "s",], re.findall(r"\d+", rating_line))})
44+
ratings.append({})
45+
for rating in rating_line.strip("{}").split(","):
46+
category, value = rating.split("=")
47+
ratings[-1][category] = int(value)
48+
49+
return workflows, ratings
50+
51+
52+
# Ranges are inclusive
53+
def find_acceptable_ranges(workflows, workflow=WORKFLOW_START, step=0, ranges={"x": [RANGE_MIN, RANGE_MAX], "m": [RANGE_MIN, RANGE_MAX], "a": [RANGE_MIN, RANGE_MAX], "s": [RANGE_MIN, RANGE_MAX]}):
54+
workflow_step = workflows[workflow][step]
55+
if len(workflow_step) == 1:
56+
if workflow_step[0] == WORKFLOW_ACCEPT:
57+
return [ranges]
58+
elif workflow_step[0] == WORKFLOW_REJECT:
59+
return []
60+
else:
61+
return find_acceptable_ranges(workflows, workflow_step[0], 0, ranges)
62+
63+
category, cmp, value, target = workflow_step
64+
65+
# Recurse for step condition true
66+
ranges_cond_true = []
67+
ranges_rec_true = copy.deepcopy(ranges)
68+
if cmp == "<":
69+
ranges_rec_true[category][1] = min(ranges_rec_true[category][1], value - 1)
70+
if ranges_rec_true[category][1] < ranges_rec_true[category][0]:
71+
ranges_rec_true = None
72+
else:
73+
ranges_rec_true[category][0] = max(ranges_rec_true[category][0], value + 1)
74+
if ranges_rec_true[category][0] > ranges_rec_true[category][1]:
75+
ranges_rec_true = None
76+
if ranges_rec_true:
77+
ranges_cond_true = find_acceptable_ranges(workflows, target, 0, ranges_rec_true)
78+
79+
80+
# Recurse for step condition false
81+
ranges_cond_false = []
82+
ranges_rec_false = copy.deepcopy(ranges)
83+
if cmp == "<":
84+
ranges_rec_false[category][0] = max(ranges_rec_false[category][0], value)
85+
if ranges_rec_false[category][0] > ranges_rec_false[category][1]:
86+
ranges_rec_false = None
87+
else:
88+
ranges_rec_false[category][1] = min(ranges_rec_false[category][1], value)
89+
if ranges_rec_false[category][1] < ranges_rec_false[category][0]:
90+
ranges_rec_false = None
91+
if ranges_rec_false:
92+
ranges_cond_false = find_acceptable_ranges(workflows, workflow, step + 1, ranges_rec_false)
93+
94+
return ranges_cond_true + ranges_cond_false
95+
96+
97+
98+
def main():
99+
workflows = read_input()[0]
100+
ranges = find_acceptable_ranges(workflows)
101+
102+
combinations = sum(prod((hi - lo + 1 for lo, hi in range.values())) for range in ranges)
103+
print(combinations)
104+
105+
if __name__ == '__main__':
106+
main()

0 commit comments

Comments
 (0)