Skip to content

Commit bc770c2

Browse files
committed
[2023] Day19
1 parent 596fa73 commit bc770c2

File tree

4 files changed

+986
-0
lines changed

4 files changed

+986
-0
lines changed

.github/workflows/2023-julia.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ jobs:
2828
julia day08.jl
2929
julia day09.jl
3030
julia day15.jl
31+
julia day19.jl
3132

2023/day19.jl

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
@enum Rating X M A S
2+
3+
@enum Op LT GT
4+
5+
struct Part
6+
x::Int
7+
m::Int
8+
a::Int
9+
s::Int
10+
end
11+
12+
abstract type Target end
13+
struct TargetWorkflow <: Target
14+
name::String
15+
end
16+
struct TargetAccepted <: Target end
17+
struct TargetRejected <: Target end
18+
19+
abstract type Rule end
20+
struct Rule3 <: Rule
21+
rating::Rating
22+
op::Op
23+
num::Int
24+
target::Target
25+
end
26+
struct Rule1 <: Rule
27+
target::Target
28+
end
29+
30+
struct Workflow
31+
name::String
32+
rules::Vector{Rule}
33+
end
34+
35+
function parseTarget(s::AbstractString)::Target
36+
if s == "A" return TargetAccepted()
37+
elseif s == "R" return TargetRejected()
38+
else TargetWorkflow(s)
39+
end
40+
end
41+
42+
function parseOp(op::Char)::Op
43+
return op == '<' ? LT : GT
44+
end
45+
46+
function parseRating(s::AbstractString)::Rating
47+
if s == "x" return X
48+
elseif s == "m" return M
49+
elseif s == "a" return A
50+
else return S
51+
end
52+
end
53+
54+
function parseRule(str::AbstractString)::Rule
55+
tokens = split(str, [ '<', '>', ':' ])
56+
if length(tokens) == 1
57+
return Rule1(parseTarget(tokens[1]))
58+
else
59+
rating = parseRating(tokens[1])
60+
op = parseOp(str[length(tokens[1]) + 1])
61+
num = parse(Int, tokens[2])
62+
target = parseTarget(tokens[3])
63+
return Rule3(rating, op, num, target)
64+
end
65+
end
66+
67+
function parseInput(input)::Tuple{Vector{Workflow}, Vector{Part}}
68+
partsStartIndex = 0
69+
workflows = Workflow[]
70+
parts = Part[]
71+
for (i, line) in enumerate(input)
72+
if length(line) == 0 partsStartIndex = i + 1; break end
73+
rulesSectionIndex = findfirst('{', line)
74+
name = line[1:rulesSectionIndex-1]
75+
rulesSection = line[rulesSectionIndex+1:end-1]
76+
rules = Rule[]
77+
for r in split(rulesSection, ",")
78+
rule = parseRule(r)
79+
push!(rules, rule)
80+
end
81+
push!(workflows, Workflow(name, rules))
82+
end
83+
for pStr in input[partsStartIndex:end]
84+
tokens = split(pStr, [ '{', '}', ',' ])
85+
x = parse(Int, tokens[2][3:end])
86+
m = parse(Int, tokens[3][3:end])
87+
a = parse(Int, tokens[4][3:end])
88+
s = parse(Int, tokens[5][3:end])
89+
part = Part(x, m, a, s)
90+
push!(parts, part)
91+
end
92+
(workflows, parts)
93+
end
94+
95+
function getPartRating(part::Part, rating::Rating)::Int
96+
if rating == X return part.x
97+
elseif rating == M return part.m
98+
elseif rating == A return part.a
99+
else return part.s
100+
end
101+
end
102+
103+
function sendToWorkflow(workflowsDict::Dict{String, Workflow}, name::String, part::Part)::Union{TargetAccepted, TargetRejected}
104+
result::Target = TargetWorkflow(name)
105+
while result != TargetRejected() && result !== TargetAccepted()
106+
workflow = workflowsDict[result.name]
107+
for r in workflow.rules
108+
if r isa Rule1
109+
result = r.target
110+
else
111+
rating = getPartRating(part, r.rating)
112+
if r.op == LT
113+
rating >= r.num && continue
114+
else
115+
rating <= r.num && continue
116+
end
117+
result = r.target
118+
end
119+
break
120+
end
121+
end
122+
return result
123+
end
124+
125+
function solveA(input)
126+
workflows, parts = parseInput(input)
127+
workflowsDict::Dict{String, Workflow} = map(x -> (x.name, x), workflows) |> Dict
128+
return [ sum([ p.x, p.m, p.a, p.s ]) for p in parts if TargetAccepted() == sendToWorkflow(workflowsDict, "in", p) ] |> x -> sum(x; init = 0)
129+
end
130+
131+
function mergeRule(rule::Rule3, rng::Tuple{Int, Int})::Tuple{Int, Int}
132+
a, b = rng
133+
if rule.op == LT
134+
rule.num > b && return rng
135+
rule.num in a:b && return (a, rule.num - 1)
136+
pritnln("mergeRule WRN $rule ($a - $b)")
137+
return (0, 0)
138+
else
139+
rule.num < a && return rng
140+
rule.num in a:b && return (rule.num + 1, b)
141+
pritnln("mergeRule WRN $rule ($a - $b)")
142+
return (0, 0)
143+
end
144+
end
145+
146+
function mergeRules(acc, rule::Rule3)
147+
if rule.rating == X return (; acc..., x = mergeRule(rule, acc.x))
148+
elseif rule.rating == M return (; acc..., m = mergeRule(rule, acc.m))
149+
elseif rule.rating == A return (; acc..., a = mergeRule(rule, acc.a))
150+
else return (; acc..., s = mergeRule(rule, acc.s))
151+
end
152+
end
153+
154+
function reverseRule(rule::Rule3)::Rule3
155+
if rule.op == LT
156+
Rule3(rule.rating, GT, rule.num - 1, rule.target)
157+
else
158+
Rule3(rule.rating, LT, rule.num + 1, rule.target)
159+
end
160+
end
161+
162+
function solveB(input)
163+
workflows, _ = parseInput(input)
164+
dict::Dict{String, Workflow} = map(x -> (x.name, x), workflows) |> Dict
165+
# vector of workflow names
166+
queue = Tuple{String, Vector{Rule}}[ ("in", []) ]
167+
results = Vector{Rule3}[]
168+
while length(queue) > 0
169+
name, sourceRules = pop!(queue)
170+
# copy of rules accumulated from previous workflows
171+
rules = copy(sourceRules)
172+
# all rules accumulated for this workflow
173+
# next rules are added to previous rules which should already be reversed (LT <-> GT)
174+
prevRules = Rule3[]
175+
for r in dict[name].rules
176+
newRules = [ rules..., prevRules... ]
177+
if r.target isa TargetAccepted
178+
if r isa Rule3
179+
push!(newRules, r)
180+
end
181+
push!(results, newRules)
182+
end
183+
if r.target isa TargetWorkflow
184+
if r isa Rule3
185+
push!(newRules, r)
186+
end
187+
push!(queue, (r.target.name, newRules))
188+
end
189+
r isa Rule3 && push!(prevRules, reverseRule(r))
190+
end
191+
end
192+
combinations = 0
193+
limit = 4000
194+
for rules in results
195+
(; x, m, a, s) = reduce((acc, a) -> mergeRules(acc, a), rules; init = (x = (1, limit), m = (1, limit), a = (1, limit), s = (1, limit)))
196+
combinations += prod([ z[2] - z[1] + 1 for z in [ x, m, a ,s ]])
197+
end
198+
combinations
199+
end
200+
201+
function main(file = "day19.txt")
202+
input = collect(eachline(file))
203+
# input = strip(read(file, String))
204+
# 446935
205+
println("Solving Day19A...")
206+
println(solveA(input))
207+
# 141882534122898
208+
println("Solving Day19B...")
209+
println(solveB(input))
210+
end
211+
212+
if !isinteractive()
213+
main("day19s.txt")
214+
end
215+
216+
function repl()
217+
# file = "day19s.txt"
218+
file = "day19.txt"
219+
println("file: $file")
220+
main(file)
221+
end

0 commit comments

Comments
 (0)