Advent of Code 2023
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Utils
defmodule AOC do
def solve(module) do
for part <- 1..2 do
apply(module, :solve, [part])
|> then(&IO.inspect("Part #{part}: #{inspect(&1)}"))
end
end
end
Day 1
{:ok, day_1_input} =
KinoAOC.download_puzzle("2023", "1", System.fetch_env!("LB_AOC_SESSION"))
defmodule Day1 do
@input day_1_input |> String.split()
@digits_table ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
|> Enum.with_index()
|> Map.new(fn {k, v} -> {k, "#{v + 1}"} end)
def solve(1) do
@input
|> Enum.map(&get_digits/1)
|> Enum.sum()
end
def solve(2) do
@input
|> Enum.map(&normalize/1)
|> Enum.sum()
end
defp get_digits(line) do
Regex.scan(~r/\d/, line)
|> then(fn ln -> List.first(ln) ++ List.last(ln) end)
|> Enum.join()
|> String.to_integer()
end
defp normalize(line) do
pattern = @digits_table |> Map.keys() |> Enum.join("|")
first =
("[0-9]|" <> pattern)
|> Regex.compile!()
|> Regex.run(line)
|> List.first()
|> convert_digit()
last =
("[0-9]|" <> (pattern |> String.reverse()))
|> Regex.compile!()
|> Regex.run(line |> String.reverse())
|> List.first()
|> String.reverse()
|> convert_digit()
(first <> last) |> String.to_integer()
end
defp convert_digit(word) do
if Enum.member?(Map.values(@digits_table), word) do
word
else
Map.get(@digits_table, word)
end
end
end
AOC.solve(Day1)
Day 2
{:ok, day_2_input} =
KinoAOC.download_puzzle("2023", "2", System.fetch_env!("LB_AOC_SESSION"))
defmodule Day2 do
@input day_2_input
@limits %{
"red" => 12,
"green" => 13,
"blue" => 14
}
def solve(1) do
@input
|> parse_games()
|> Enum.map(&are_rounds_possible?/1)
|> Enum.with_index(1)
|> Enum.reduce(0, fn {possible?, idx}, acc ->
if possible?, do: acc + idx, else: acc
end)
end
def solve(2) do
@input
|> parse_games()
|> Enum.map(&minimum_cubes/1)
|> Enum.map(fn rounds ->
rounds
|> Enum.map(fn {_color, count} -> count end)
|> Enum.product()
end)
|> Enum.sum()
end
defp parse_games(input) do
for game <- String.splitter(input, "\n", trim: true) do
~r/(?:Game \d+: )(.*)/
|> Regex.run(game, capture: :all_but_first)
|> List.first()
|> String.split(";\s")
end
end
defp are_rounds_possible?(rounds) do
for round <- rounds do
String.split(round, ",\s")
|> Enum.map(fn turn ->
[num, color] = String.split(turn)
Map.get(@limits, color) >= String.to_integer(num)
end)
|> Enum.all?()
end
|> Enum.all?()
end
defp minimum_cubes(rounds) do
rounds
|> Enum.flat_map(&String.split(&1, ",\s"))
|> Enum.group_by(
fn turn -> Regex.run(~r/(red|green|blue)/, turn) |> List.first() end,
fn turn -> Regex.run(~r/\d+/, turn) |> List.first() |> String.to_integer() end
)
|> Enum.map(fn {k, v} -> {k, Enum.max(v)} end)
end
end
AOC.solve(Day2)
Day 4
{:ok, day_4_input} =
KinoAOC.download_puzzle("2023", "4", System.fetch_env!("LB_AOC_SESSION"))
day_4_sample =
"""
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"""
|> String.trim()
defmodule Day4 do
@input day_4_input |> String.split("\n", trim: true)
def solve(1) do
@input
|> Enum.map(fn card ->
card
|> String.replace(~r/^Card\s+\d+:\s+/, "")
|> String.split(" | ")
|> Enum.map(&string_to_set/1)
|> then(&apply(MapSet, :intersection, &1))
|> MapSet.size()
end)
|> Enum.filter(fn x -> x > 0 end)
|> Enum.map(fn winners ->
for n <- 1..winners, reduce: 1 do
acc -> if n == 1, do: acc, else: acc * 2
end
end)
|> Enum.sum()
end
defp string_to_set(s) do
String.split(s) |> Enum.map(&String.to_integer/1) |> MapSet.new()
end
end
Day4.solve(1)