aoc 2025
Library
defmodule AOC do
# int("123") = 123
def int(s), do: String.to_integer(s)
# str(123) = "123"
def str(x), do: to_string(x)
# dup(1, 3) -> [1,1,1]
def dup(x, n), do: for(_ <- 1..n//1, do: x)
# int2d("1 2 3\n4 5 6") = [[1,2,3], [4,5,6]]
def int2d(s, sep \\ ~r/\D+/, sep2 \\ "\n") do
String.split(s, sep2, trim: true)
|> Enum.map(fn s -> for i <- String.split(s, sep), i != "", do: int(i) end)
end
# mod(5, 3) = 2
def mod(a, b), do: Integer.mod(a, b)
# divmod(5, 3) = {1, 2}
def divmod(a, b), do: {div(a, b), mod(a, b)}
def puts(s), do: IO.puts(inspect s, printable_limit: :infinity) && s
def putm(m) do
for({{h, w}, _} <- [Enum.max m], i <- 0..h, do: IO.puts(for j <- 0..w, do: m[{i, j}])) && m
end
def s2map(s) do
for {r, i} <- String.split(s, "\n", trim: true) |> Enum.with_index(),
{c, j} <- String.to_charlist(r) |> Enum.with_index(),
into: %{}, do: {{i, j}, c}
end
def up(p \\ {0, 0}), do: add2(p, {-1, 0})
def down(p \\ {0, 0}), do: add2(p, {1, 0})
def left(p \\ {0, 0}), do: add2(p, {0, -1})
def right(p \\ {0, 0}), do: add2(p, {0, 1})
def add2({x, y}, {u, v}), do: {x + u, y + v}
def sub2({x, y}, {u, v}), do: {x - u, y - v}
def mul2({x, y}, k), do: {x * k, y * k}
def dot2({x1, y1}, {x2, y2}), do: x1 * x2 + y1 * y2
def eq2({x1, y1}, {x2, y2}), do: x1 == x2 && y1 == y2
def lt2({x1, y1}, {x2, y2}), do: x1 < x2 && y1 < y2
def gt2({x1, y1}, {x2, y2}), do: x1 > x2 && y1 > y2
def l1norm({x1, y1}, {x2, y2}), do: abs(x1 - x2) + abs(y1 - y2)
def dir4(), do: [{1, 0}, {0, 1}, {-1, 0}, {0, -1}]
def dir8(), do: [{-1, -1}, {-1, 0}, {-1, 1}, { 0, -1}, { 0, 1}, { 1, -1}, { 1, 0}, { 1, 1}]
def move(p, ds \\ dir4()), do: for(d <- ds, do: add2(p, d))
def set(enum \\ []), do: MapSet.new(enum)
defmacro cache(key, do: expr) do
quote do
Process.get(unquote(key)) || unquote(expr) |> tap(&Process.put(unquote(key), &1))
end
end
# connected components
def cc(g, ps \\ [], c \\ set(), cs \\ [])
def cc(g, [], c, cs) do
case Enum.take(g, 1) do
[] -> [c | cs]
[{p, e}] -> cc(Map.delete(g, p), e, set([p]), [])
end
end
def cc(g, [p|ps], c, cs) do
case Map.pop(g, p) do
{nil, g} -> cc(g, ps, c, cs)
{e, g} -> cc(g, e ++ ps, MapSet.put(c, p), cs)
end
end
# keyword to graph [{k,v}] -> %{k => [v]}
def k2g(ks) do
for {p, q} <- ks, {p, q} <- [{p, q}, {q, p}], reduce: %{} do
g -> Map.update(g, p, [q], &[q | &1])
end
end
def str2d(s, r1 \\ ~r/\n/, r2 \\ ~r/\W+/) do
for s <- String.split(s, r1, trim: true), do: String.split(s, r2, trim: true)
end
end
2025
defmodule Day1 do
import AOC
def part1(input) do
for [s] <- input |> str2d(), reduce: {50, 0} do
{dial, count} ->
{d, n} = String.split_at(s, 1)
sign = d == "R" && 1 || -1
n = int(n)
dial = mod(dial + sign * n, 100)
count = count + (dial == 0 && 1 || 0)
{dial, count}
end
|> elem(1)
end
def part2(input) do
for [s] <- input |> str2d(), reduce: {50, 0} do
{dial, count} ->
{d, n} = String.split_at(s, 1)
sign = d == "R" && 1 || -1
n = int(n)
dial2 = mod(dial + sign * n, 100)
dist = sign > 0 && 100 - dial || (dial == 0 && 100 || dial)
m = n - dist
count2 = count + (m > 0 && div(m, 100) + 1 || 0) + (m == 0 && 1 || 0)
{dial2, count2}
end
|> elem(1)
end
end
input = "L68\nL30\nR48\nL5\nR60\nL55\nL1\nL99\nR14\nL82\n"
Day1.part1(input) |> IO.inspect(label: :day1_part1)
Day1.part2(input) |> IO.inspect(label: :day1_part2)
defmodule Day2 do
import AOC
def part1(input) do
input
|> str2d(",", "-")
|> Enum.flat_map(fn [a, b] -> int(a)..int(b) end)
|> Enum.filter(fn s ->
s = str(s)
n = String.length(s)
{a, b} = String.split_at(s, div(n, 2))
a == b
end)
|> Enum.sum()
end
def part2(input) do
input
|> str2d(",", "-")
|> Enum.flat_map(fn [a, b] -> int(a)..int(b) end)
|> Enum.filter(&repeat(to_charlist(&1), 1) > 1)
|> Enum.sum()
end
def repeat(cs, n) when length(cs) == n, do: 1
def repeat(cs, n) do
Enum.chunk_every(cs, n)
|> Enum.uniq()
|> case do
[_] -> div(length(cs), n)
_ -> repeat(cs, n + 1)
end
end
end
input = "11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124"
Day2.part1(input) |> IO.inspect(label: :day2_part1)
Day2.part2(input) |> IO.inspect(label: :day2_part2)
defmodule Day3 do
import AOC
def part1(input) do
input
|> int2d("")
|> Enum.sum_by(&find(&1, 2, 0))
end
def part2(input) do
input
|> int2d("")
|> Enum.sum_by(&find(&1, 12, 0))
end
def find(_, 0, r), do: r
def find(a, n, r) do
{d, i} = Enum.slice(a, 0..-n//1) |> Enum.with_index() |> Enum.max_by(&elem(&1, 0))
Enum.slice(a, i+1..-1//1) |> find(n - 1, r * 10 + d)
end
end
input = "987654321111111\n811111111111119\n234234234234278\n818181911112111"
Day3.part1(input) |> IO.inspect(label: :day3_part1)
Day3.part2(input) |> IO.inspect(label: :day3_part2)
defmodule Day4 do
import AOC
def part1(input) do
input |> s2map() |> find() |> length()
end
def part2(input) do
input |> s2map() |> solve2(0)
end
def find(m) do
for({p, c} <- m, c == ?@, Enum.count(dir8(), &m[add2(p, &1)] == ?@) < 4, do: p)
end
def solve2(m, acc) do
case find(m) do
[] -> acc
ps -> for(p <- ps, reduce: m, do: (m -> %{m | p => ?.})) |> solve2(acc + length(ps))
end
end
end
input = "..@@.@@@@.\n@@@.@.@.@@\n@@@@@.@.@@\n@.@@@@..@.\n@@.@@@@.@@\n.@@@@@@@.@\n.@.@.@.@@@\n@.@@@.@@@@\n.@@@@@@@@.\n@.@.@@@.@.\n"
Day4.part1(input) |> IO.inspect(label: :day4_part1)
Day4.part2(input) |> IO.inspect(label: :day4_part2)
defmodule Day5 do
import AOC
def part1(input) do
[rs, is] = input |> String.trim() |> String.split("\n\n")
rs = int2d(rs)
is = int2d(is) |> List.flatten()
Enum.count(is, fn i ->
Enum.any?(rs, fn [a, b] -> a <= i && i <= b end)
end)
end
def part2(input) do
input |> String.split("\n\n") |> hd() |> int2d() |> Enum.sort()
|> Enum.reduce([], &union/2)
|> Enum.sum_by(fn [a, b] -> b - a + 1 end)
end
def union([a, b], []), do: [[a, b]]
def union([a, b], [[c, d] | rs]) when a > d, do: [[a, b], [c, d] | rs]
def union([a, b], [[c, d] | rs]), do: [[min(a, c), max(b, d)] | rs]
end
input = "3-5\n10-14\n16-20\n12-18\n\n1\n5\n8\n11\n17\n32\n"
Day5.part1(input) |> IO.inspect(label: :day5_part1)
Day5.part2(input) |> IO.inspect(label: :day5_part2)
defmodule Day6 do
import AOC
def part1(input) do
{a, [b]} = str2d(input, "\n", ~r/ +/) |> Enum.split(-1)
a = Enum.zip_with(a, &for(i <- &1, do: int i))
b = Enum.map(b, &get_op/1)
Enum.zip_reduce(a, b, 0, fn r, op, acc -> op.(r) + acc end)
end
def part2(input) do
{a, [b]} = str2d(input, "\n", "") |> Enum.split(-1)
a = Enum.zip_with(a, &String.trim str(&1)) |> Enum.join("\n") |> int2d("\n", "\n\n")
b = Enum.flat_map(b, &List.wrap get_op &1)
Enum.zip_reduce(a, b, 0, fn r, op, acc -> op.(r) + acc end)
end
def get_op(s), do: %{"+" => &Enum.sum/1, "*" => &Enum.product/1}[s]
end
input = "123 328 51 64 \n 45 64 387 23 \n 6 98 215 314\n* + * + \n"
Day6.part1(input) |> IO.inspect(label: :day6_part1)
Day6.part2(input) |> IO.inspect(label: :day6_part2)
defmodule Day7 do
import AOC
def part1(input) do
m = s2map(input)
for {p, v} <- Enum.sort(m), reduce: Map.put(m, :c, 0) do m ->
case {v, m[up p], m.c} do
{?., b, _} when b in [?S, ?|] -> %{m | p => ?|}
{?^, b, c} when b in [?S, ?|] -> %{m | left(p) => ?|, right(p) => ?|, c: c + 1}
_ -> m
end
end
|> Map.get(:c)
end
def part2(input) do
m = s2map(input)
[s] = for {p, ?S} <- m, do: p
solve2(m, s)
end
def solve2(m, p) do
cache p do
case m[p] do
nil -> 1
?^ -> solve2(m, left(p)) + solve2(m, right(p))
_ -> solve2(m, down(p))
end
end
end
end
input = ".......S.......\n...............\n.......^.......\n...............\n......^.^......\n...............\n.....^.^.^.....\n...............\n....^.^...^....\n...............\n...^.^...^.^...\n...............\n..^...^.....^..\n...............\n.^.^.^.^.^...^.\n...............\n"
Day7.part1(input) |> IO.inspect(label: :day7_part1)
Day7.part2(input) |> IO.inspect(label: :day7_part2)
defmodule Day8 do
import AOC
def part1(input) do
ps = int2d(input)
for(p <- ps, q <- ps, p < q, do: {dist(p, q), p, q})
|> Enum.sort()
|> Enum.take(length(ps) < 100 && 10 || 1000)
|> Enum.map(fn {_, p, q} -> {p, q} end)
|> k2g()
|> all_cc([])
|> Enum.map(&Enum.count/1)
|> Enum.sort()
|> Enum.take(-3)
|> Enum.product()
end
def part2(input) do
ps = int2d(input)
for(p <- ps, q <- ps, p < q, do: {dist(p, q), p, q})
|> Enum.sort()
|> solve2(length(ps))
end
def dist([a, b, c], [d, e, f]), do: (a-d)**2+(b-e)**2+(c-f)**2
def all_cc(g, acc) when map_size(g) == 0, do: acc
def all_cc(g, acc) do
c = cc(g) |> hd() |> Enum.to_list()
Map.drop(g, c) |> all_cc([c | acc])
end
def solve2([{_, p, q}|rest], n, g \\ %{}) do
g = g |> Map.update(p, [q], &[q | &1]) |> Map.update(q, [p], &[p | &1])
(cc(g) |> hd() |> Enum.count()) == n && hd(p)*hd(q) || solve2(rest, n, g)
end
end
input = "162,817,812\n57,618,57\n906,360,560\n592,479,940\n352,342,300\n466,668,158\n542,29,236\n431,825,988\n739,650,466\n52,470,668\n216,146,977\n819,987,18\n117,168,530\n805,96,715\n346,949,466\n970,615,88\n941,993,340\n862,61,35\n984,92,344\n425,690,689\n"
Day8.part1(input) |> IO.inspect(label: :day8_part1)
Day8.part2(input) |> IO.inspect(label: :day8_part2)
defmodule Day9 do
import AOC
def part1(input) do
ps = input |> int2d() |> Enum.map(&List.to_tuple/1)
Enum.max for p <- ps, q <- ps, p < q, do: area(p, q)
end
def part2(input) do
ps = input |> int2d() |> Enum.map(&List.to_tuple/1)
es = Enum.zip(ps, tl(ps) ++ [hd ps])
{xs, ys} = Enum.unzip(ps)
xs = Enum.uniq(xs) |> Enum.sort()
ys = Enum.uniq(ys) |> Enum.sort()
(for p <- ps, q <- ps, p < q, valid(p, q, es, xs, ys), do: {area(p, q), p, q})
|> Enum.max()
|> IO.inspect(charlists: :as_lists)
end
def area({x1, y1}, {x2, y2}), do: (abs(x1 - x2) + 1) * (abs(y1 - y2) + 1)
def valid({x1, y1}, {x2, y2}, es, xs, ys) do
{x1, x2} = Enum.min_max([x1, x2])
{y1, y2} = Enum.min_max([y1, y2])
xs = for x <- xs, x1 <= x, x <= x2, do: x
ys = for y <- ys, y1 <= y, y <= y2, do: y
for {x3, x4} <- Enum.zip(xs, tl(xs)), {y3, y4} <- Enum.zip(ys, tl(ys)) do
valid2({(x3+x4)/2, (y3+y4)/2}, es)
end
|> Enum.all?(& &1)
end
def valid2({x, y}, es) do
cache {x, y} do
for {{x1, y1}, {x2, y2}} <- es, y1 == y2, y < y1, (x-x1)*(x-x2) < 0, reduce: nil, do: (b -> !b)
end
end
end
input = "7,1\n11,1\n11,7\n9,7\n9,5\n2,5\n2,3\n7,3\n"
Day9.part1(input) |> IO.inspect(label: :day9_part1)
Day9.part2(input) |> IO.inspect(label: :day9_part2)
defmodule Day10 do
import AOC
def part1(input) do
for parts <- input |> str2d("\n", " ") do
state = hd(parts) |> String.slice(1..-2//1) |> to_charlist() |> Enum.with_index() |> Enum.into(%{}, fn {c, i} -> {i, c == ?#} end)
buttons = Enum.slice(parts, 1..-2//1) |> Enum.join("\n") |> int2d()
solve1([state], buttons, 0)
end
|> Enum.sum()
end
def part2(input) do
input
end
def solve1(states, buttons, step) do
if step > 10, do: exit(1)
case Enum.any?(states, &goal/1) do
true -> step
_ ->
Enum.flat_map(states, fn state ->
for ks <- buttons do
for k <- ks, reduce: state do
m -> %{m | k => !m[k]}
end
end
end)
|> Enum.uniq()
|> solve1(buttons, step + 1)
end
end
def goal(state) do
Enum.all?(state, fn {_, v} -> v == false end)
end
end
input = "[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}\n[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}\n[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}\n"
Day10.part1(input) |> IO.inspect(label: :part1)
Day10.part2(input) |> IO.inspect(label: :part2)
defmodule Day11 do
import AOC
def part1(input), do: parse(input) |> solve1("you")
def part2(input), do: parse(input) |> solve2("svr", ["fft", "dac", "out"])
def parse(input), do: (for [k | vs] <- input |> str2d("\n", ~r/:? /), into: %{}, do: {k, vs})
def solve1(g, p), do: (p == "out" && 1 || Enum.sum for q <- g[p], do: solve1(g, q))
def solve2(g, p, [p|t]), do: solve2(g, p, t)
def solve2(_, _, []), do: 1
def solve2(g, p, c), do: (cache {p, c} do Enum.sum for q <- g[p] || [], do: solve2(g, q, c) end)
end
input = "aaa: you hhh\nyou: bbb ccc\nbbb: ddd eee\nccc: ddd eee fff\nddd: ggg\neee: out\nfff: out\nggg: out\nhhh: ccc fff iii\niii: out\n"
input2 = "svr: aaa bbb\naaa: fft\nfft: ccc\nbbb: tty\ntty: ccc\nccc: ddd eee\nddd: hub\nhub: fff\neee: dac\ndac: fff\nfff: ggg hhh\nggg: out\nhhh: out\n"
Day11.part1(input) |> IO.inspect(label: :part1)
Day11.part2(input2) |> IO.inspect(label: :part2)