2023 Day 07
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(7, 2023)
Part 1
test_input = """
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
"""
defmodule P1 do
@rank ~w/2 3 4 5 6 7 8 9 T J Q K A/ |> Enum.zip(0..12) |> Map.new()
def solve(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn str ->
[hand, bid_str] = String.split(str)
{hand, String.to_integer(bid_str)}
end)
|> Enum.sort_by(fn {hand, _bid} ->
{hand_type(hand), left_to_right_rank(hand)}
end)
|> Enum.with_index(1)
|> Enum.map(fn {{_hand, bid}, rank} -> bid * rank end)
|> Enum.sum()
end
def left_to_right_rank(hand) do
hand |> String.graphemes() |> Enum.map(&Map.get(@rank, &1))
end
def hand_type(hand) do
case hand |> String.graphemes() |> Enum.frequencies() |> Map.values() |> Enum.sort() do
[5] -> 7
[1, 4] -> 6
[2, 3] -> 5
[1, 1, 3] -> 4
[1, 2, 2] -> 3
[1, 1, 1, 2] -> 2
[1, 1, 1, 1, 1] -> 1
end
end
end
day.input |> P1.solve()
Part 2
defmodule P2 do
@rank ~w/J 2 3 4 5 6 7 8 9 T Q K A/ |> Enum.zip(0..12) |> Map.new()
def solve(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn str ->
[hand, bid_str] = String.split(str)
{hand, String.to_integer(bid_str)}
end)
|> Enum.sort_by(fn {hand, _bid} ->
hand_freqs = hand |> String.graphemes() |> Enum.frequencies()
{hand_type(hand_freqs), left_to_right_rank(hand)}
end)
|> Enum.with_index(1)
|> Enum.map(fn {{_hand, bid}, rank} -> bid * rank end)
|> Enum.sum()
end
def left_to_right_rank(hand) do
hand |> String.graphemes() |> Enum.map(&Map.get(@rank, &1))
end
def hand_type(%{"J" => 5}), do: 7
def hand_type(%{"J" => jokers} = hand) do
no_jokers = Map.delete(hand, "J")
[{k, v} | _] = Enum.sort_by(no_jokers, fn {_k, v} -> v end, :desc)
no_jokers
|> Map.put(k, v + jokers)
|> hand_type()
end
def hand_type(hand_freqs) do
case hand_freqs |> Map.values() |> Enum.sort() do
[5] -> 7
[1, 4] -> 6
[2, 3] -> 5
[1, 1, 3] -> 4
[1, 2, 2] -> 3
[1, 1, 1, 2] -> 2
[1, 1, 1, 1, 1] -> 1
end
end
end
day.input |> P2.solve()