Advent of code 2024
Day 1
defmodule Day_1 do
def input do
"""
3 4
4 3
2 5
1 3
3 9
3 3
"""
File.read!("day1.txt")
end
def get_lists() do
Day_1.input()
|> String.split()
|> Enum.map(&String.to_integer(&1))
|> Enum.chunk_every(2)
|> Enum.reduce({[], []}, fn [i, j], {acc_i, acc_j} ->
{[i | acc_i], [j | acc_j]}
end)
end
def run_part1() do
{l1, l2} = get_lists()
l1 = Enum.sort(l1)
l2 = Enum.sort(l2)
Enum.zip(l2, l1)
|> Enum.map(fn
{i, j} when i > j -> i - j
{i, j} -> j - i
end)
|> Enum.sum()
end
def run_part2() do
{l1, l2} = get_lists()
fq = Enum.frequencies(l2)
Enum.map(l1, fn l -> l * Map.get(fq, l, 0) end)
|> Enum.sum()
end
end
Day_1.run_part1()
Day_1.run_part2()
Day 2
defmodule Day_2 do
def input() do
"""
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
"""
File.read!("day2.txt")
end
def calculate(list, opts \\ []) do
cond do
Enum.sort(list, :asc) == list ->
:increase
Enum.sort(list, :desc) == list ->
:decrease
true ->
:unknown
end
|> is_safe?(list, opts)
end
def get_level_differ(list, head) do
Enum.reduce(list, {[], head}, fn l, {i, j} ->
{i ++ [l - j], l}
end)
|> elem(0)
end
def is_safe?(_, _, opts \\ [])
def is_safe?(:increase, [head | list], opts) do
result =
get_level_differ(list, head)
|> Enum.all?(fn i -> i in [1, 2, 3] end)
if Keyword.get(opts, :recheck, true) do
result
|> is_is_safe?([head | list])
else
result
end
end
def is_safe?(:decrease, [head | list], opts) do
result =
get_level_differ(list, head)
|> Enum.all?(fn i -> i in [-1, -2, -3] end)
if Keyword.get(opts, :recheck, true) do
result
|> is_is_safe?([head | list])
else
result
end
end
def is_safe?(_, list, opts) do
if Keyword.get(opts, :recheck, true) do
false
|> is_is_safe?(list)
else
false
end
end
def is_is_safe?(true, _) do
true
end
def is_is_safe?(false, list) do
list
|> Enum.with_index()
|> Enum.map(fn {_, index} ->
List.delete_at(list, index)
|> calculate(recheck: false)
end)
|> Enum.any?()
end
def run_part1() do
Day_2.input()
|> String.split("\n", trim: true)
|> Enum.map(fn l ->
String.split(l)
|> Enum.map(&String.to_integer(&1))
|> Day_2.calculate(recheck: false)
end)
|> Enum.count(fn r -> r == true end)
end
def run_part2() do
Day_2.input()
|> String.split("\n", trim: true)
|> Enum.map(fn l ->
String.split(l)
|> Enum.map(&String.to_integer(&1))
|> Day_2.calculate()
end)
|> Enum.count(fn r -> r == true end)
end
end
Day_2.run_part1()
Day_2.run_part2()
Day 3
defmodule Day_3 do
def input do
"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
File.read!("day3.txt")
end
def input2 do
"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
end
def calculate(x) do
Regex.named_captures(~r/mul\((?\d{1,3}),(?\d{1,3})\)/, x)
|> Map.values()
|> Enum.map(&String.to_integer(&1))
|> Enum.product()
end
def run_part1() do
Regex.scan(~r/mul\(\d{1,3},\d{1,3}\)/, Day_3.input())
|> Enum.map(fn [x] -> calculate(x) end)
|> Enum.sum()
end
def run_part2() do
Regex.scan(~r/mul\(\d{1,3},\d{1,3}\)|do\(\)|don't\(\)/, Day_3.input())
|> Enum.reduce({[], "do()"}, fn [i], {acc, x} ->
cond do
i == "do()" -> {acc, i}
i == "don't()" -> {acc, i}
x == "do()" -> {acc ++ [i], x}
true -> {acc, x}
end
end)
|> elem(0)
|> Enum.map(fn x -> calculate(x) end)
|> Enum.sum()
end
end
Day_3.run_part1()
Day_3.run_part2()
Day 4
defmodule Day_4 do
def input() do
"""
MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX
"""
File.read!("day4.txt")
end
def get_word(str) do
word = String.split(str, "", trim: true)
word_reverse = Enum.reverse(word)
{word, word_reverse, 0..(length(word) - 1)}
end
def get_grid() do
Day_4.input()
|> String.split("\n", trim: true)
|> Enum.map(fn i -> String.split(i, "", trim: true) end)
end
def gen_diagonal(grid, i, j) do
unless i < 0 || j < 0 do
(Enum.at(grid, i) || [])
|> Enum.at(j)
end
end
@spec add_count(Integer.t(), boolean()) :: Integer.t()
def add_count(count, res) do
if res do
count + 1
else
count
end
end
def run_part1() do
grid = Day_4.get_grid()
{word, word_reverse, from_to} = Day_4.get_word("XMAS")
Enum.with_index(grid)
|> Enum.reduce(0, fn {y, i}, count ->
Enum.with_index(y)
|> Enum.reduce(count, fn {_x, j}, inner_count ->
horizontal = Enum.map(from_to, fn g -> grid |> Enum.at(i) |> Enum.at(j + g) end)
vertical = Enum.map(from_to, fn g -> (grid |> Enum.at(i + g) || []) |> Enum.at(j) end)
diagonal_1 = Enum.map(from_to, &Day_4.gen_diagonal(grid, i + &1, j + &1))
diagonal_2 = Enum.map(from_to, &Day_4.gen_diagonal(grid, i + &1, j - &1))
inner_count
|> Day_4.add_count(horizontal == word)
|> Day_4.add_count(horizontal == word_reverse)
|> Day_4.add_count(vertical == word)
|> Day_4.add_count(vertical == word_reverse)
|> Day_4.add_count(diagonal_1 == word)
|> Day_4.add_count(diagonal_1 == word_reverse)
|> Day_4.add_count(diagonal_2 == word)
|> Day_4.add_count(diagonal_2 == word_reverse)
end)
end)
end
def run_part2() do
grid = Day_4.get_grid()
{word, word_reverse, from_to} = Day_4.get_word("MAS")
Enum.with_index(grid)
|> Enum.reduce(0, fn {y, i}, count ->
Enum.with_index(y)
|> Enum.reduce(count, fn {_x, j}, inner_count ->
diagonal_1 = Enum.map(from_to, &Day_4.gen_diagonal(grid, i + &1, j + &1))
diagonal_2 = Enum.map(from_to, &Day_4.gen_diagonal(grid, i + &1, j - &1 + 2))
inner_count
|> Day_4.add_count(diagonal_1 == word && diagonal_2 == word)
|> Day_4.add_count(diagonal_1 == word && diagonal_2 == word_reverse)
|> Day_4.add_count(diagonal_1 == word_reverse && diagonal_2 == word)
|> Day_4.add_count(diagonal_1 == word_reverse && diagonal_2 == word_reverse)
end)
end)
end
end
Day_4.run_part1()
Day_4.run_part2()
Day 5
defmodule Day_5 do
def input do
"""
47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13
75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47
"""
end
def process_input() do
File.read!("day5.txt")
|> String.split("\n", trim: true)
|> Enum.reduce({[], []}, fn i, {acc_x, acc_y} ->
String.contains?(i, "|")
|> if do
[start, ends] =
String.split(i, "|")
|> Enum.map(&String.to_integer(&1))
{[{start, ends} | acc_x], acc_y}
else
{acc_x, [String.split(i, ",") |> Enum.map(&String.to_integer(&1)) | acc_y]}
end
end)
|> case do
{ordering, print_order} ->
{Enum.reverse(ordering)
|> Day_5.to_map(), Enum.reverse(print_order)}
end
end
def to_map(ordering) do
Enum.reduce(ordering, %{}, fn {from, to}, acc ->
Map.get_and_update(acc, from, fn curr ->
if curr == nil do
{curr, [to]}
else
{curr, [to | curr]}
end
end)
|> elem(1)
end)
end
def run_part1(print_order, ordering_map) do
Enum.filter(print_order, fn list -> to_check?(list, ordering_map) end)
|> get_sum()
end
def run_part2(print_order, ordering_map) do
Enum.reject(print_order, fn list -> to_check?(list, ordering_map) end)
|> Enum.map(fn list -> re_order(list, ordering_map) end)
|> get_sum()
end
def to_check?([head | rest], map) do
values = Map.get(map, head, [])
Enum.all?(rest, fn r -> r in values end)
|> if do
if rest == [] do
true
else
to_check?(rest, map)
end
else
false
end
end
def re_order(list, map, index \\ 0, acc \\ [])
def re_order([], _, _, acc) do
acc
end
def re_order(list, map, index, acc) do
{key, rest} = List.pop_at(list, index)
l = Map.get(map, key) || []
Enum.all?(rest, fn r -> r in l end)
|> if do
re_order(rest, map, index, acc ++ [key])
else
if index + 1 >= length(list) do
re_order(list, map, 0, acc)
else
re_order(list, map, index + 1, acc)
end
end
end
def get_sum(result) when is_list(result) do
Enum.reduce(result, 0, fn list, sum ->
index = trunc((length(list) - 1) / 2)
sum + Enum.at(list, index)
end)
end
end
{ordering, print_order} = Day_5.process_input()
Day_5.run_part1(print_order, ordering)
Day_5.run_part2(print_order, ordering)