day19
This commit is contained in:
parent
d0b7ca24ef
commit
6bdcb771fb
3 changed files with 208 additions and 0 deletions
10
bench/day19_bench.exs
Normal file
10
bench/day19_bench.exs
Normal file
|
@ -0,0 +1,10 @@
|
|||
{_, puzzle_input} = File.read("./input/2023_19.txt")
|
||||
|
||||
{commands, _} = Mix.Tasks.Day19.parse(puzzle_input, &Mix.Tasks.Day19.build_range_comparator/1)
|
||||
|
||||
Benchee.run(%{
|
||||
"day19_part2" => fn -> Mix.Tasks.Day19.solve_part_2(commands) end
|
||||
})
|
||||
|
||||
#Name ips average deviation median 99th %
|
||||
#day19_part2 1.47 K 681.65 μs ±25.77% 603.99 μs 1300.13 μs
|
180
lib/2023/day19.ex
Normal file
180
lib/2023/day19.ex
Normal file
|
@ -0,0 +1,180 @@
|
|||
defmodule Mix.Tasks.Day19 do
|
||||
def run(args) do
|
||||
[part | _] = args
|
||||
{_, puzzle_input} = File.read("./input/2023_19.txt")
|
||||
part = if part == "1", do: :part1, else: :part2
|
||||
|
||||
{commands, toys} = parse(puzzle_input, case part do
|
||||
:part1 -> &build_comparator/1
|
||||
:part2 -> &build_range_comparator/1
|
||||
end)
|
||||
|
||||
cond do
|
||||
part == :part1 -> solve_part_1(commands, toys)
|
||||
part == :part2 -> solve_part_2(commands)
|
||||
end |> IO.puts()
|
||||
end
|
||||
|
||||
def build_comparator(map) do
|
||||
%{"comp" => comp, "facet" => facet, "target" => target, "thresh" => thresh} = map
|
||||
|
||||
if comp == "" do
|
||||
fn _ -> target end
|
||||
else
|
||||
comp =
|
||||
if comp == ">" do
|
||||
fn a, b -> a > b end
|
||||
else
|
||||
comp == "<"
|
||||
fn a, b -> a < b end
|
||||
end
|
||||
|
||||
thresh = String.to_integer(thresh)
|
||||
|
||||
fn toy ->
|
||||
if toy |> Map.get(facet) |> comp.(thresh) do
|
||||
target
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_range_comparator(map) do
|
||||
%{"comp" => comp, "facet" => facet, "target" => target, "thresh" => thresh} = map
|
||||
|
||||
if comp == "" do
|
||||
fn toy -> [{toy, target}] end
|
||||
else
|
||||
thresh = String.to_integer(thresh)
|
||||
|
||||
fn toy ->
|
||||
toy
|
||||
|> Map.get(facet)
|
||||
|> RangeUtil.split_at(thresh, if(comp == ">", do: :lb, else: :ub))
|
||||
|> Enum.map(fn new_range ->
|
||||
if comp == ">" and new_range.first > thresh do
|
||||
{new_range, target}
|
||||
else
|
||||
if comp == "<" and new_range.last < thresh do
|
||||
{new_range, target}
|
||||
else
|
||||
{new_range, nil}
|
||||
end
|
||||
end
|
||||
end)
|
||||
|> Enum.map(fn {new_range, target} ->
|
||||
{toy |> Map.put(facet, new_range), target}
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse(puzzle_input, comparator_builder) do
|
||||
[commands, toys] = puzzle_input |> String.split("\n\n")
|
||||
|
||||
commands =
|
||||
commands
|
||||
|> String.split("\n")
|
||||
|> Enum.map(fn l ->
|
||||
[label, inner | _] = String.split(l, ["{", "}"])
|
||||
inner = String.split(inner, ",")
|
||||
|
||||
steps =
|
||||
inner
|
||||
|> Enum.map(fn i ->
|
||||
r = ~r/(((?P<facet>[xmas])(?P<comp>[<>])(?P<thresh>\d+):)?(?P<target>\w+),?)/
|
||||
Regex.named_captures(r, i) |> comparator_builder.()
|
||||
end)
|
||||
|
||||
{label, steps}
|
||||
end)
|
||||
|> Map.new()
|
||||
|
||||
toys =
|
||||
toys
|
||||
|> String.split("\n")
|
||||
|> Stream.reject(&(&1 == ""))
|
||||
|> Enum.map(fn t ->
|
||||
[_, u, _] = String.split(t, ["{", "}"])
|
||||
|
||||
u
|
||||
|> String.split(",")
|
||||
|> Stream.map(fn f -> f |> String.split("=") end)
|
||||
|> Enum.map(fn [f, t] ->
|
||||
{f, String.to_integer(t)}
|
||||
end)
|
||||
|> Map.new()
|
||||
end)
|
||||
|
||||
{commands, toys}
|
||||
end
|
||||
|
||||
def map_toy(toy, label, map) do
|
||||
if label === "A" or label === "R" do
|
||||
label
|
||||
else
|
||||
map
|
||||
|> Map.get(label)
|
||||
|> Enum.reduce_while(
|
||||
nil,
|
||||
fn f, _ ->
|
||||
dest = f.(toy)
|
||||
{if(is_nil(dest), do: :cont, else: :halt), dest}
|
||||
end
|
||||
)
|
||||
|> then(fn dest -> map_toy(toy, dest, map) end)
|
||||
end
|
||||
end
|
||||
|
||||
def map_toy_range(toy_range, label, commands) do
|
||||
if label === "A" or label === "R" do
|
||||
[{toy_range, label}]
|
||||
else
|
||||
commands
|
||||
|> Map.get(label)
|
||||
|> Enum.reduce_while(
|
||||
[{toy_range, nil}],
|
||||
fn f, acc ->
|
||||
new =
|
||||
acc
|
||||
|> Enum.flat_map(fn {range, target} ->
|
||||
if is_nil(target) do
|
||||
f.(range)
|
||||
else
|
||||
[{range, target}]
|
||||
end
|
||||
end)
|
||||
|
||||
{
|
||||
if(Enum.all?(new, fn {_, dest} -> !is_nil(dest) end), do: :halt, else: :cont),
|
||||
new
|
||||
}
|
||||
end
|
||||
)
|
||||
|> Enum.flat_map(fn {r, t} -> map_toy_range(r, t, commands) end)
|
||||
end
|
||||
end
|
||||
|
||||
def solve_part_1(toys, commands) do
|
||||
toys
|
||||
|> Enum.filter(fn t -> map_toy(t, "in", commands) === "A" end)
|
||||
|> Enum.map(fn t ->
|
||||
t |> Map.values() |> Enum.sum()
|
||||
end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def solve_part_2(commands) do
|
||||
toy_range = %{"x" => 1..4000, "m" => 1..4000, "a" => 1..4000, "s" => 1..4000}
|
||||
|
||||
map_toy_range(toy_range, "in", commands)
|
||||
|> Stream.filter(fn {_, r} -> r === "A" end)
|
||||
|> Stream.map(&elem(&1, 0))
|
||||
|> Stream.map(fn r ->
|
||||
Map.values(r) |> Enum.reduce(1, fn rr, acc -> acc * Range.size(rr) end)
|
||||
end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
end
|
|
@ -57,4 +57,22 @@ defmodule RangeUtil do
|
|||
r2.first >= r1.first and r2.last >= r1.last -> Range.new(r2.first, r1.last)
|
||||
end
|
||||
end
|
||||
|
||||
@spec split_at(range :: Range.t(), value :: integer(), bound :: :lb | :ub) :: [Range.t()]
|
||||
def split_at(range, value, bound) do
|
||||
if value <= range.first or value >= range.last do
|
||||
[range]
|
||||
else
|
||||
ls = min(range.first, value - 1)
|
||||
le = min(range.last, value - 1)
|
||||
rs = max(range.first, value + 1)
|
||||
re = max(range.last, value + 1)
|
||||
|
||||
if bound == :lb do
|
||||
[ls..value, rs..re]
|
||||
else
|
||||
[ls..le, value..re]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue