Powered by AppSignal & Oban Pro

aoc 2025

aoc2025.livemd

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) &amp;&amp; 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}])) &amp;&amp; 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 &amp;&amp; y1 == y2
  def lt2({x1, y1}, {x2, y2}), do: x1 < x2 &amp;&amp; y1 < y2
  def gt2({x1, y1}, {x2, y2}), do: x1 > x2 &amp;&amp; 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(&amp;Process.put(unquote(key), &amp;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], &amp;[q | &amp;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" &amp;&amp; 1 || -1
        n = int(n)
        dial = mod(dial + sign * n, 100)
        count = count + (dial == 0 &amp;&amp; 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" &amp;&amp; 1 || -1
        n = int(n)
        dial2 = mod(dial + sign * n, 100)
        dist = sign > 0 &amp;&amp; 100 - dial || (dial == 0 &amp;&amp; 100 || dial)
        m = n - dist
        count2 = count + (m > 0 &amp;&amp; div(m, 100) + 1 || 0) + (m == 0 &amp;&amp; 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(&amp;repeat(to_charlist(&amp;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(&amp;find(&amp;1, 2, 0))
  end
  def part2(input) do
    input
    |> int2d("")
    |> Enum.sum_by(&amp;find(&amp;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(&amp;elem(&amp;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(), &amp;m[add2(p, &amp;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 &amp;&amp; i <= b end) 
    end)
  end
  def part2(input) do
    input |> String.split("\n\n") |> hd() |> int2d() |> Enum.sort()
    |> Enum.reduce([], &amp;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, &amp;for(i <- &amp;1, do: int i))
    b = Enum.map(b, &amp;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, &amp;String.trim str(&amp;1)) |> Enum.join("\n") |> int2d("\n", "\n\n")
    b = Enum.flat_map(b, &amp;List.wrap get_op &amp;1)
    Enum.zip_reduce(a, b, 0, fn r, op, acc -> op.(r) + acc end)
  end
  def get_op(s), do: %{"+" => &amp;Enum.sum/1, "*" => &amp;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 &amp;&amp; 10 || 1000)
    |> Enum.map(fn {_, p, q} -> {p, q} end)
    |> k2g()
    |> all_cc([])
    |> Enum.map(&amp;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], &amp;[q | &amp;1]) |> Map.update(q, [p], &amp;[p | &amp;1])
    (cc(g) |> hd() |> Enum.count()) == n &amp;&amp; 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(&amp;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(&amp;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?(&amp; &amp;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, &amp;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" &amp;&amp; 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)