Skip to content

Commit 8eeaf07

Browse files
committed
Refactor 2025
1 parent 20fc5b6 commit 8eeaf07

21 files changed

Lines changed: 7971 additions & 0 deletions

lib/2025/day_01.ex

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
defmodule AdventOfCode.Y2025.Day01 do
2+
@moduledoc """
3+
--- Day 1: Secret Entrance ---
4+
Problem Link: https://adventofcode.com/2025/day/1
5+
Difficulty: e
6+
Tags: simulation safe-dial
7+
"""
8+
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
10+
def input, do: InputReader.read_from_file(2025, 1)
11+
12+
def run(input \\ input()) do
13+
instructions = parse(input)
14+
15+
{solve_1(instructions), solve_2(instructions)}
16+
end
17+
18+
defp solve_1(instructions) do
19+
{count, _final} =
20+
Enum.reduce(instructions, {0, 50}, fn {dir, steps}, {cnt, pos} ->
21+
delta = if dir == "L", do: -steps, else: steps
22+
new_pos = Integer.mod(pos + delta, 100)
23+
new_cnt = if new_pos == 0, do: cnt + 1, else: cnt
24+
{new_cnt, new_pos}
25+
end)
26+
27+
count
28+
end
29+
30+
defp solve_2(instructions) do
31+
{count, _final} =
32+
Enum.reduce(instructions, {0, 50}, fn {dir, steps}, {cnt, pos} ->
33+
hits = count_hits(pos, dir, steps)
34+
delta = if dir == "L", do: -steps, else: steps
35+
new_pos = Integer.mod(pos + delta, 100)
36+
{cnt + hits, new_pos}
37+
end)
38+
39+
count
40+
end
41+
42+
defp count_hits(pos, "R", steps) do
43+
Integer.floor_div(pos + steps, 100) - Integer.floor_div(pos, 100)
44+
end
45+
46+
defp count_hits(pos, "L", steps) do
47+
Integer.floor_div(pos - 1, 100) - Integer.floor_div(pos - steps - 1, 100)
48+
end
49+
50+
def parse(data \\ input()) do
51+
data
52+
|> Transformers.lines()
53+
|> Enum.map(fn line ->
54+
{side, steps} = String.split_at(line, 1)
55+
{side, String.to_integer(steps)}
56+
end)
57+
end
58+
end

lib/2025/day_02.ex

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
defmodule AdventOfCode.Y2025.Day02 do
2+
@moduledoc """
3+
--- Day 2: Gift Shop ---
4+
Problem Link: https://adventofcode.com/2025/day/2
5+
Difficulty: m
6+
Tags: simulation generation string-manipulation math
7+
"""
8+
alias AdventOfCode.Helpers.InputReader
9+
10+
def input, do: InputReader.read_from_file(2025, 2)
11+
12+
def run(input \\ input()) do
13+
ranges = parse(input)
14+
merged = merge_ranges(ranges)
15+
16+
max_val = merged |> Enum.map(fn {_, hi} -> hi end) |> Enum.max()
17+
max_len = max_val |> to_string() |> String.length()
18+
19+
solution_1 = solve(merged, max_len, :exactly_twice)
20+
solution_2 = solve(merged, max_len, :at_least_twice)
21+
22+
{solution_1, solution_2}
23+
end
24+
25+
defp solve(ranges, max_len, rule) do
26+
candidates =
27+
for len <- 2..max_len,
28+
k <- find_multipliers(len, rule),
29+
p = div(len, k),
30+
min_x = Integer.pow(10, p - 1),
31+
max_x = Integer.pow(10, p) - 1,
32+
x <- min_x..max_x do
33+
s = to_string(x)
34+
String.duplicate(s, k) |> String.to_integer()
35+
end
36+
|> MapSet.new()
37+
38+
candidates
39+
|> Enum.filter(fn id -> in_ranges?(id, ranges) end)
40+
|> Enum.sum()
41+
end
42+
43+
defp find_multipliers(len, :exactly_twice) do
44+
if rem(len, 2) == 0, do: [2], else: []
45+
end
46+
47+
defp find_multipliers(len, :at_least_twice) do
48+
for k <- 2..len, rem(len, k) == 0, do: k
49+
end
50+
51+
defp in_ranges?(id, ranges) do
52+
Enum.any?(ranges, fn {lo, hi} -> id >= lo and id <= hi end)
53+
end
54+
55+
defp merge_ranges(ranges) do
56+
ranges
57+
|> Enum.sort()
58+
|> Enum.reduce([], fn
59+
{lo, hi}, [] ->
60+
[{lo, hi}]
61+
62+
{lo, hi}, [{prev_lo, prev_hi} | rest] ->
63+
if lo <= prev_hi + 1 do
64+
[{prev_lo, max(hi, prev_hi)} | rest]
65+
else
66+
[{lo, hi}, {prev_lo, prev_hi} | rest]
67+
end
68+
end)
69+
|> Enum.reverse()
70+
end
71+
72+
def parse(data \\ input()) do
73+
data
74+
|> String.split(~r/[, \n]/, trim: true)
75+
|> Enum.map(fn line ->
76+
[lo, hi] = String.split(line, "-")
77+
{String.to_integer(lo), String.to_integer(hi)}
78+
end)
79+
end
80+
end

lib/2025/day_03.ex

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
defmodule AdventOfCode.Y2025.Day03 do
2+
@moduledoc """
3+
--- Day 3: Lobby ---
4+
Problem Link: https://adventofcode.com/2025/day/3
5+
Difficulty: m
6+
Tags: greedy stack subsequence-optimization
7+
"""
8+
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
10+
def input, do: InputReader.read_from_file(2025, 3)
11+
12+
def run(input \\ input()) do
13+
banks = parse(input)
14+
15+
{solve(banks, 2), solve(banks, 12)}
16+
end
17+
18+
defp solve(banks, k) do
19+
banks
20+
|> Enum.map(fn digits -> max_subsequence(digits, k) end)
21+
|> Enum.sum()
22+
end
23+
24+
defp max_subsequence(digits, k) do
25+
total_len = length(digits)
26+
can_drop = total_len - k
27+
28+
{stack, _can_drop} =
29+
Enum.reduce(digits, {[], can_drop}, fn d, {stk, drop} ->
30+
{new_stk, new_drop} = pop_greedy(stk, d, drop)
31+
{[d | new_stk], new_drop}
32+
end)
33+
34+
stack
35+
|> Enum.reverse()
36+
|> Enum.take(k)
37+
|> Enum.join()
38+
|> String.to_integer()
39+
end
40+
41+
defp pop_greedy([top | rest], d, drop) when drop > 0 and d > top do
42+
pop_greedy(rest, d, drop - 1)
43+
end
44+
45+
defp pop_greedy(stk, _, drop), do: {stk, drop}
46+
47+
def parse(data \\ input()) do
48+
data
49+
|> Transformers.lines()
50+
|> Enum.map(fn line ->
51+
line
52+
|> String.graphemes()
53+
|> Enum.map(&String.to_integer/1)
54+
end)
55+
end
56+
end

lib/2025/day_05.ex

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
defmodule AdventOfCode.Y2025.Day05 do
2+
@moduledoc """
3+
--- Day 5: Cafeteria ---
4+
Problem Link: https://adventofcode.com/2025/day/5
5+
Difficulty: e
6+
Tags: simulation ranges interval-merging
7+
"""
8+
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
10+
def input, do: InputReader.read_from_file(2025, 5)
11+
12+
def run(input \\ input()) do
13+
[ranges_raw, ids_raw] = String.split(input, ~r/\n\s*\n/, parts: 2)
14+
ranges = parse_ranges(ranges_raw)
15+
ids = parse_ids(ids_raw)
16+
17+
{run_1(ranges, ids), run_2(ranges)}
18+
end
19+
20+
defp run_1(ranges, ids) do
21+
merged = merge_ranges(ranges)
22+
23+
ids
24+
|> Enum.count(fn id -> in_ranges?(id, merged) end)
25+
end
26+
27+
defp run_2(ranges) do
28+
ranges
29+
|> merge_ranges()
30+
|> Enum.map(fn {lo, hi} -> hi - lo + 1 end)
31+
|> Enum.sum()
32+
end
33+
34+
defp in_ranges?(id, ranges) do
35+
Enum.any?(ranges, fn {lo, hi} -> id >= lo and id <= hi end)
36+
end
37+
38+
defp merge_ranges(ranges) do
39+
ranges
40+
|> Enum.sort()
41+
|> Enum.reduce([], fn
42+
{lo, hi}, [] ->
43+
[{lo, hi}]
44+
45+
{lo, hi}, [{prev_lo, prev_hi} | rest] ->
46+
if lo <= prev_hi + 1 do
47+
[{prev_lo, max(hi, prev_hi)} | rest]
48+
else
49+
[{lo, hi}, {prev_lo, prev_hi} | rest]
50+
end
51+
end)
52+
|> Enum.reverse()
53+
end
54+
55+
defp parse_ranges(data) do
56+
data
57+
|> Transformers.lines()
58+
|> Enum.map(fn line ->
59+
[lo, hi] = String.split(line, "-")
60+
{String.to_integer(lo), String.to_integer(hi)}
61+
end)
62+
end
63+
64+
defp parse_ids(data) do
65+
data
66+
|> Transformers.lines()
67+
|> Enum.map(&String.to_integer/1)
68+
end
69+
70+
def parse(data \\ input()) do
71+
data
72+
|> Transformers.lines()
73+
end
74+
end

lib/2025/day_06.ex

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
defmodule AdventOfCode.Y2025.Day06 do
2+
@moduledoc """
3+
--- Day 6: Trash Compactor ---
4+
Problem Link: https://adventofcode.com/2025/day/6
5+
Difficulty: m
6+
Tags: grid parsing math
7+
"""
8+
9+
def input, do: File.read!("priv/input_files/2025_6.txt")
10+
11+
def run(input \\ input()) do
12+
grid =
13+
input
14+
|> String.split("\n")
15+
|> Enum.reject(&(&1 == ""))
16+
17+
width = grid |> Enum.map(&String.length/1) |> Enum.max()
18+
grid = Enum.map(grid, fn line -> String.pad_trailing(line, width) end)
19+
20+
blocks = identify_blocks(grid, width)
21+
22+
{solve_part1(grid, blocks), solve_part2(grid, blocks)}
23+
end
24+
25+
defp identify_blocks(grid, width) do
26+
non_space_cols =
27+
Enum.reduce(0..(width - 1), [], fn j, acc ->
28+
if Enum.all?(grid, fn row -> String.at(row, j) == " " end) do
29+
acc
30+
else
31+
[j | acc]
32+
end
33+
end)
34+
|> Enum.reverse()
35+
36+
non_space_cols
37+
|> Enum.chunk_while(
38+
[],
39+
fn
40+
j, [] -> {:cont, [j]}
41+
j, [last | _] = chunk when j == last + 1 -> {:cont, [j | chunk]}
42+
j, chunk -> {:cont, Enum.reverse(chunk), [j]}
43+
end,
44+
fn
45+
[] -> {:cont, []}
46+
chunk -> {:cont, Enum.reverse(chunk), []}
47+
end
48+
)
49+
|> Enum.map(fn chunk -> {List.first(chunk), length(chunk)} end)
50+
end
51+
52+
defp solve_part1(grid, blocks) do
53+
height = length(grid)
54+
55+
blocks
56+
|> Enum.map(fn {start, len} ->
57+
numbers =
58+
for row_idx <- 0..(height - 2) do
59+
grid
60+
|> Enum.at(row_idx)
61+
|> String.slice(start, len)
62+
|> String.replace(" ", "")
63+
end
64+
|> Enum.reject(&(&1 == ""))
65+
|> Enum.map(&String.to_integer/1)
66+
67+
op = get_op(grid, {start, len})
68+
apply_op(numbers, op)
69+
end)
70+
|> Enum.sum()
71+
end
72+
73+
defp solve_part2(grid, blocks) do
74+
height = length(grid)
75+
76+
blocks
77+
|> Enum.reverse()
78+
|> Enum.map(fn {start, len} ->
79+
numbers =
80+
for j <- (start + len - 1)..start//-1 do
81+
digits =
82+
for row_idx <- 0..(height - 2) do
83+
String.at(Enum.at(grid, row_idx), j)
84+
end
85+
|> Enum.join()
86+
|> String.replace(" ", "")
87+
88+
if digits == "", do: nil, else: String.to_integer(digits)
89+
end
90+
|> Enum.reject(&is_nil/1)
91+
92+
op = get_op(grid, {start, len})
93+
apply_op(numbers, op)
94+
end)
95+
|> Enum.sum()
96+
end
97+
98+
defp get_op(grid, {start, len}) do
99+
grid
100+
|> List.last()
101+
|> String.slice(start, len)
102+
|> String.trim()
103+
end
104+
105+
defp apply_op([n | rest], "+") do
106+
Enum.reduce(rest, n, &Kernel.+/2)
107+
end
108+
109+
defp apply_op([n | rest], "*") do
110+
Enum.reduce(rest, n, &Kernel.*/2)
111+
end
112+
113+
def parse(data \\ input()) do
114+
data
115+
|> String.split("\n", trim: true)
116+
end
117+
end

0 commit comments

Comments
 (0)