2023 Day 15
Mix.install([
{:req, "~> 0.4.5"},
{:vega_lite, "~> 0.1.8"},
{:kino_vega_lite, "~> 0.1.11"}
])
alias VegaLite, as: Vl
defmodule AOC do
@aoc_session System.fetch_env!("LB_AOC_SESSION")
def day(day, year) when day in 1..25 do
headers = [cookie: "session=#{@aoc_session}"]
req = Req.new(base_url: "https://adventofcode.com/#{year}/day/#{day}", headers: headers)
input = Req.get!(req, url: "/input").body
if input =~ "Please don't repeatedly request this endpoint before it unlocks",
do: {:error, "Day #{day} hasn't been unlocked yet"},
else: {:ok, %{req: req, input: input}}
end
def submit(answer, %{req: req}, part \\ :part_1) when part in [:part_1, :part_2] do
part = if part == :part_2, do: 2, else: 1
if !part_already_completed?(part, req) do
body = Req.post!(req, url: "/answer", form: [level: part, answer: answer]).body
cond do
body =~ "That's the right answer" -> {:correct, answer}
body =~ "your answer is too low" -> {:too_low, answer}
body =~ "your answer is too high" -> {:too_high, answer}
:else -> {:incorrect, answer}
end
else
{:already_completed_part, answer}
end
end
defp part_already_completed?(part, req) do
body = Req.get!(req).body
cond do
body =~ "Both parts of this puzzle are complete!" -> true
body =~ "The first half of this puzzle is complete!" -> part == 1
:else -> false
end
end
def nums(str) do
Regex.scan(~r/\d+/, str) |> Enum.map(fn [x] -> String.to_integer(x) end)
end
end
{:ok, day} = AOC.day(15, 2023)
Part 1
test_input = "rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7"
defmodule P1 do
def solve(input) do
input
|> String.replace(~r/\s+/, "")
|> String.split(",", trim: true)
|> Enum.map(&hash/1)
|> Enum.sum()
end
@doc """
iex> P1.hash("HASH")
52
"""
def hash(str) when is_binary(str) do
hash(String.to_charlist(str), 0)
end
def hash([], acc), do: acc
def hash([ch | rest], acc) do
acc = acc + ch
acc = acc * 17
hash(rest, rem(acc, 256))
end
end
# test_input |> P1.solve()
day.input |> P1.solve() |> AOC.submit(day)
Part 2
defmodule P2 do
def solve(input) do
input
|> String.replace(~r/\s+/, "")
|> String.split(",", trim: true)
|> Enum.map(fn str ->
case Regex.run(~r/([a-z]+)=(\d+)/, str) do
[_, label, focal_length] -> {:add, label, String.to_integer(focal_length)}
_ -> {:remove, str |> String.split("-") |> hd()}
end
end)
# for each step, update the boxes by adding or removing a lens
|> Enum.reduce(%{}, fn
{:add, label, focal_length}, boxes -> add_lens(label, focal_length, boxes)
{:remove, label}, boxes -> remove_lens(label, boxes)
end)
# add up "focusing power" of all the lenses
|> Enum.map(fn {box, lenses} ->
lenses
|> Enum.with_index(1)
|> Enum.map(fn {{_label, focus_length}, i} -> focus_length * i end)
|> Enum.sum()
|> then(fn n -> n * (box + 1) end)
end)
|> Enum.sum()
end
def remove_lens(label, boxes) do
box = P1.hash(label)
lenses = boxes |> Map.get(box, []) |> Keyword.delete(String.to_atom(label))
Map.put(boxes, box, lenses)
end
def add_lens(label, foc, boxes) do
box = P1.hash(label)
label = String.to_atom(label)
lenses = boxes |> Map.get(box, []) |> Keyword.update(label, foc, fn _ -> foc end)
Map.put(boxes, box, lenses)
end
end
test_input |> P2.solve()
# day.input |> P2.solve()