Day 19
Solution
defmodule D19 do
@workflow_regex ~r/(?\w+){(?.+),(?\w+)}/
@cond_regex ~r/(?[xmas])(?[><])(?\d+):(?\w+)/
@part_regex ~r/{x=(?\d+),m=(?\d+),a=(?\d+),s=(?\d+)}/
def parse_input(input) do
[workflows, parts] = input |> String.trim() |> String.split("\n\n")
workflows =
workflows
|> String.split("\n")
|> Enum.map(fn workflow ->
%{
"name" => name,
"conds" => conds,
"otherwise" => otherwise
} = Regex.named_captures(@workflow_regex, workflow)
conds =
String.split(conds, ",")
|> Enum.map(fn condition ->
parts = Regex.named_captures(@cond_regex, condition)
%{
cat: parts["cat"],
op: parts["op"],
send_to: parts["send_to"],
val: String.to_integer(parts["val"])
}
end)
{name, %{conds: conds, otherwise: otherwise}}
end)
|> Enum.into(%{})
parts =
parts
|> String.split("\n")
|> Enum.map(fn part ->
Regex.named_captures(@part_regex, part)
|> Enum.map(fn {k, v} -> {k, String.to_integer(v)} end)
|> Enum.into(%{})
end)
%{workflows: workflows, parts: parts}
end
def rating(part) when is_map(part), do: Map.values(part) |> Enum.sum()
def run(part, workflows) when is_map(part) and is_map(workflows) do
run(part, workflows, "in")
end
def run(_part, _workflows, "A"), do: "A"
def run(_part, _workflows, "R"), do: "R"
def run(part, workflows, workflow) do
current = workflows[workflow]
matching_condition =
Enum.find(current.conds, fn condition ->
part_value = part[condition.cat]
case condition.op do
">" -> part_value > condition.val
"<" -> part_value < condition.val
end
end)
if is_nil(matching_condition) do
run(part, workflows, current.otherwise)
else
run(part, workflows, matching_condition.send_to)
end
end
def p1(input) do
Enum.filter(input.parts, fn part ->
run(part, input.workflows) == "A"
end)
|> Enum.map(&rating/1)
|> Enum.sum()
end
def p2(input) do
ranges = for key <- ["x", "m", "a", "s"], do: {key, 1..4000}, into: %{}
count_acceptable(ranges, input.workflows, "in")
end
def count_acceptable(_ranges, _workflows, "R"), do: 0
def count_acceptable(ranges, _workflows, "A") do
ranges |> Map.values() |> Enum.map(&Range.size/1) |> Enum.product()
end
def count_acceptable(ranges, workflows, workflow) do
current = workflows[workflow]
{remaining, acceptable} =
Enum.reduce(
current.conds,
{ranges, 0},
fn condition, {ranges, acceptable} ->
current_cat_range = ranges[condition.cat]
{pass, fail} =
case condition.op do
">" ->
pass = Map.put(ranges, condition.cat, (condition.val + 1)..current_cat_range.last)
fail = Map.put(ranges, condition.cat, current_cat_range.first..condition.val)
{pass, fail}
"<" ->
pass =
Map.put(ranges, condition.cat, current_cat_range.first..(condition.val - 1))
fail = Map.put(ranges, condition.cat, condition.val..current_cat_range.last)
{pass, fail}
end
{fail, acceptable + count_acceptable(pass, workflows, condition.send_to)}
end
)
acceptable + count_acceptable(remaining, workflows, current.otherwise)
end
end
{:module, D19, <<70, 79, 82, 49, 0, 0, 42, ...>>, {:count_acceptable, 3}}
Tests
ExUnit.start(autorun: false)
defmodule D08Test do
use ExUnit.Case, async: true
@test_input D19.parse_input("""
px{a<2006:qkq,m>2090:A,rfg}
pv{a>1716:R,A}
lnx{m>1548:A,A}
rfg{s<537:gd,x>2440:R,A}
qs{s>3448:A,lnx}
qkq{x<1416:A,crn}
crn{x>2662:A,R}
in{s<1351:px,qqz}
qqz{s>2770:qs,m<1801:hdj,R}
gd{a>3333:R,R}
hdj{m>838:A,pv}
{x=787,m=2655,a=1222,s=2876}
{x=1679,m=44,a=2067,s=496}
{x=2036,m=264,a=79,s=2244}
{x=2461,m=1339,a=466,s=291}
{x=2127,m=1623,a=2188,s=1013}
""")
@input Path.join(__DIR__, "inputs/d19") |> File.read!() |> D19.parse_input()
test "part 1 works" do
assert D19.p1(@test_input) == 19114
assert D19.p1(@input) == 399_284
end
test "part 2 works" do
assert D19.p2(@test_input) == 167_409_079_868_000
assert D19.p2(@input) == 121_964_982_771_486
end
end
ExUnit.run()
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures
Randomized with seed 140176
%{excluded: 0, failures: 0, skipped: 0, total: 2}