lud
Advent of Code 2023 - Day 19
A rather verbose solution but simple enough I guess.
Most Liked
antoine-duchenet
Here’s my solution, it went pretty well !
import Input
defmodule Day19 do
def part1(input) do
{map, parts} = parse_input(input)
parts
|> Enum.map(&walk(map, "in", &1))
|> Enum.zip(parts)
|> Enum.map(&score/1)
|> Enum.sum()
end
defp walk(map, wf_name, part) do
conds = Map.fetch!(map, wf_name)
conds
|> Enum.find(fn
{field, ">", thresh, _} ->
part
|> Map.fetch!(field)
|> Kernel.>(thresh)
{field, "<", thresh, _} ->
part
|> Map.fetch!(field)
|> Kernel.<(thresh)
_ ->
true
end)
|> then(fn
{_, _, _, res} -> res
default -> default
end)
|> case do
"A" -> "A"
"R" -> "R"
new_wf_name -> walk(map, new_wf_name, part)
end
end
defp score({"R", _}), do: 0
defp score({"A", %{"x" => x, "m" => m, "a" => a, "s" => s}}), do: x + m + a + s
def part2(input) do
{map, _} = parse_input(input)
combs(map, Map.fetch!(map, "in"), %{
"x" => {1, 4001},
"m" => {1, 4001},
"a" => {1, 4001},
"s" => {1, 4001}
})
end
defp combs(map, [{field, op, thresh, res} | tail], threshs) do
{new_thresh, rev_thresh} = split_treshs(threshs, field, op, thresh)
combs(map, res, new_thresh) + combs(map, tail, rev_thresh)
end
defp combs(map, [key], threshs), do: combs(map, key, threshs)
defp combs(map, "A", threshs) do
threshs
|> Enum.map(fn {_, {min, max}} -> max - min end)
|> Enum.map(&max(&1, 0))
|> Enum.reduce(1, &(&1 * &2))
end
defp combs(map, "R", threshs), do: 0
defp combs(map, key, threshs), do: combs(map, Map.fetch!(map, key), threshs)
defp split_treshs(threshs, field, "<", thresh) do
new_threshs =
Map.update!(threshs, field, fn {min, max} ->
{min, min(max, thresh)}
end)
rev_threshs =
Map.update!(threshs, field, fn {min, max} ->
{max(min, thresh), max}
end)
{new_threshs, rev_threshs}
end
defp split_treshs(threshs, field, ">", thresh) do
new_threshs =
Map.update!(threshs, field, fn {min, max} ->
{max(min, thresh + 1), max}
end)
rev_threshs =
Map.update!(threshs, field, fn {min, max} ->
{min, min(max, thresh + 1)}
end)
{new_threshs, rev_threshs}
end
defp parse_input([workflows, parts]) do
{
workflows |> Enum.map(&parse_workflow/1) |> Map.new(),
Enum.map(parts, &parse_part/1)
}
end
defp parse_workflow(input) do
input
|> Utils.splitrim(~r/[{},]/)
|> Enum.map(&Utils.splitrim(&1, ":"))
|> Enum.map(fn
[solo] ->
solo
[<<field::bytes-size(1), op::bytes-size(1), thresh::binary>>, res] ->
{field, op, String.to_integer(thresh), res}
end)
|> then(fn [name | conds] -> {name, conds} end)
end
defp parse_part(input) do
input
|> Utils.splitrim(~r/[{},]/)
|> Enum.map(&Utils.splitrim(&1, "="))
|> Enum.map(fn [field, value] -> {field, String.to_integer(value)} end)
|> Map.new()
end
def run() do
part2(~i[19]c)
end
def bench() do
Benchmark.mesure_milliseconds(&run/0)
end
end
Input is just a collection of sigils (such as ~i) to manipulate inputs (like returning parts separated by an empty line).
lud
Part 2 wants you to count all possible combinations of x, m, a, s (between 1 and 4000) that would be accepted by the workflows.
So basically this:
for x <- 1..4000, m <- 1..4000, s <- 1..4000, a <- 1..4000, reduce: 0 do
count ->
if accepted_part?(%{x: x, m: m, s: s, a: a}, worfklows) do
count + 1
else
count
end
end
woojiahao
Got to use Agent today, pretty fun day honestly:
Part 1 was relatively straightforward, part 2 was a little more challenging in the data storing front, but the actual calculation was quite straightforward
midouest
For Part 2 I first recursively walked the tree from "in" and built up a list of branches that lead to "A". Then I flattened the conditions in each branch into a ranges by category. Lastly I found the number of permutations of each branch using Enum.product and summed all of the permutations for each branch. I probably could have combined the first two steps, but I was thinking about the problem as “find all the acceptance branches, then find the number of permutations in each branch”.
Part 1
defmodule Part1 do
def parse(input) do
[workflows, ratings] = String.split(input, "\n\n")
workflows =
for line <- String.split(workflows, "\n", trim: true), into: %{} do
[name, rules] = String.split(line, ["{", "}"], trim: true)
rules =
for rule <- String.split(rules, ",") do
case Regex.run(~r/(?:(\w)([<>])(\d+):)?(\w+)/, rule, capture: :all_but_first) do
["", "", "", output] ->
output
[category, op, threshold, output] ->
threshold = String.to_integer(threshold)
{category, op, threshold, output}
end
end
{name, rules}
end
ratings =
for line <- String.split(ratings, "\n", trim: true) do
for field <- String.split(line, ["{", "}", ","], trim: true), into: %{} do
[key, value] = String.split(field, "=")
{key, String.to_integer(value)}
end
end
{workflows, ratings}
end
def eval(rating, workflows), do: eval(rating, workflows, "in")
def eval(_, _, "R"), do: false
def eval(_, _, "A"), do: true
def eval(rating, workflows, <<name::binary>>), do: eval(rating, workflows, workflows[name])
def eval(rating, workflows, [<<name::binary>>]), do: eval(rating, workflows, name)
def eval(rating, workflows, [{category, op, threshold, output} | rules]) do
fun = if op == "<", do: &Kernel.</2, else: &Kernel.>/2
next = if fun.(rating[category], threshold), do: output, else: rules
eval(rating, workflows, next)
end
end
{workflows, ratings} = Part1.parse(input)
ratings
|> Enum.filter(&Part1.eval(&1, workflows))
|> Enum.flat_map(&Map.values/1)
|> Enum.sum()
Part 2
defmodule Part2 do
def acceptance(workflows), do: acceptance("in", workflows) |> Enum.map(&flatten/1)
def acceptance("R", _), do: false
def acceptance("A", _), do: true
def acceptance(<<name::binary>>, workflows), do: acceptance(workflows[name], workflows)
def acceptance([<<name::binary>>], workflows), do: acceptance(name, workflows)
def acceptance([{category, op, threshold, output} | rest], workflows) do
left =
output
|> acceptance(workflows)
|> prepend({category, op, threshold})
right_op = if op === "<", do: ">=", else: "<="
right =
rest
|> acceptance(workflows)
|> prepend({category, right_op, threshold})
left ++ right
end
def prepend(false, _), do: []
def prepend(true, rule), do: [[rule]]
def prepend(branches, rule), do: Enum.map(branches, &[rule | &1])
@input "xmas"
|> String.graphemes()
|> Enum.map(&{&1, 1..4000})
|> Map.new()
def flatten(branch), do: flatten(branch, @input)
def flatten([], acc), do: acc
def flatten([{category, op, threshold} | branch], acc) do
lo..hi = acc[category]
range =
case op do
">" -> max(threshold + 1, lo)..hi
">=" -> max(threshold, lo)..hi
"<" -> lo..min(threshold - 1, hi)
"<=" -> lo..min(threshold, hi)
end
acc = %{acc | category => range}
flatten(branch, acc)
end
end
Part2.acceptance(workflows)
|> Enum.map(fn rating ->
rating
|> Map.values()
|> Enum.map(&Enum.count/1)
|> Enum.product()
end)
|> Enum.sum()
tywhisky
For part2 I used MapSet.intersection/2, which didn’t perform very well, but the whole coding experience was very smooth, no debugging, and I got the right results in one go. The idea is also very easy to understand.







