|
| 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