KeithFrost

KeithFrost

2025 Dec 03

Lobby

defmodule Joltage do
  def parse_line(s) do
    String.trim(s)
      |> to_charlist()
      |> Enum.map(fn ch ->
        if ch <= ?9 and ch >= ?0 do
          ch - ?0
        end
      end)
  end

  def parse(lines) do
    Enum.map(lines, &parse_line/1)
  end

  def max_prefix(bank) do
    [_last | rbank] = Enum.reverse(bank)
    Enum.max(rbank)
  end

  def max_joltage(bank) do
    max1 = max_prefix(bank)
    [_max1 | tail] = Enum.drop_while(bank, fn j -> j < max1 end)
    max1 * 10 + Enum.max(tail)
  end

  def sum_max_joltages(banks) do
    Enum.reduce(banks, 0, fn bank, sum ->
      sum + max_joltage(bank)
    end)
  end
end
test_banks = """
987654321111111
811111111111119
234234234234278
818181911112111
""" |> String.split("\n", trim: true)
  |> Joltage.parse()
  |> IO.inspect()
Enum.map(test_banks, &Joltage.max_joltage/1)
  |> IO.inspect(charlists: :as_lists)
Joltage.sum_max_joltages(test_banks)
input_banks = File.stream!(__DIR__ <> "/dec-03-input.txt")
  |> Joltage.parse()
Joltage.sum_max_joltages(input_banks)

Part Two

defmodule Joltage2 do
  def max_prefix(bank, n) do
    Enum.reverse(bank)
      |> Enum.drop(n - 1)
      |> Enum.max()
  end
  
  @batteries 12

  def max_joltage(bank, n \\ @batteries, acc \\ 0) do
    if n < 1 do
      acc
    else
      max1 = max_prefix(bank, n)
      [_max1 | tail] = Enum.drop_while(bank, fn j -> j < max1 end)
      max_joltage(tail, n - 1, acc * 10 + max1)
    end
  end

  def sum_max_joltages(banks) do
    Enum.reduce(banks, 0, fn bank, sum ->
      sum + max_joltage(bank)
    end)
  end
end
Enum.map(test_banks, &Joltage2.max_joltage/1)
  |> IO.inspect(charlists: :as_lists)
Joltage2.sum_max_joltages(test_banks)
Joltage2.sum_max_joltages(input_banks)

Showing Posts 1 to 10

lud

lud

A bit overengineered but runs under 3ms on my machine so good enough I guess

defmodule AdventOfCode.Solutions.Y25.Day03 do
  alias AoC.Input

  def parse(input, _part) do
    input
    |> Input.stream!(trim: true)
    |> Enum.map(&Integer.digits(String.to_integer(&1)))
  end

  def part_one(banks) do
    Enum.sum_by(banks, &best_jolts_pair/1)
  end

  defp best_jolts_pair(digits) do
    {last, firsts} = List.pop_at(digits, -1)
    first = Enum.max(firsts)
    [^first | rest] = Enum.drop_while(firsts, &(&1 != first))
    second = Enum.max([last | rest])
    first * 10 + second
  end

  def part_two([first | _] = banks) do
    len = length(first)
    Enum.sum_by(banks, &best_jolts(&1, len))
  end

  defp best_jolts(bank, len) do
    init_candidate = {len, bank, 0}

    {_, _, jolts} =
      Enum.reduce(11..0//-1, init_candidate, fn need_remaining_digits, candidate ->
        {rest_len, bank_rest, jolts} = candidate
        best_candidate(bank_rest, rest_len, need_remaining_digits, jolts)
      end)

    jolts
  end

  defp best_candidate(bank, len, need_remaining_digits, jolts) do
    max_index_plus_one = len - need_remaining_digits
    {candidates, _rest} = Enum.split(bank, max_index_plus_one)

    {value, index} =
      candidates
      |> Enum.with_index()
      |> Enum.reduce({0, 0}, fn {value, index}, {best_val, best_index} ->
        case value do
          n when n > best_val -> {n, index}
          _ -> {best_val, best_index}
        end
      end)

    {_, rest} = Enum.split(bank, index + 1)
    rest_len = len - index - 1
    {rest_len, rest, jolts * 10 + value}
  end
end

Aetherus

Aetherus

My top-down dynamic programming solution for both parts (omitting the input parsing part):

defmodule AoC2025.Day3 do
  @spec solve([tuple()], pos_integer()) :: pos_integer()
  def solve(input, on_count) do
    Enum.sum_by(input, fn line ->
      dp(line, on_count - 1, 0, %{})[{on_count - 1, 0}]
    end)
  end

  @spec dp(tuple(), pow, index, memo) :: memo
        when memo: %{optional({pow, index}) => non_neg_integer()},
             pow: -1 | non_neg_integer(),
             index: non_neg_integer()
  defp dp(_line, -1, _i, memo) do
    memo
  end

  defp dp(line, _pow, i, memo) when i >= tuple_size(line) do
    memo
  end

  defp dp(line, pow, i, memo) do
    if memo[{pow, i}] do
      memo
    else
      curr = elem(line, i) * (10 ** pow)
      memo = dp(line, pow - 1, i + 1, memo)
      memo = dp(line, pow, i + 1, memo)

      cond do
        pow == 0 && !memo[{pow, i + 1}] ->
          Map.put(memo, {pow, i}, curr)

        pow == 0 ->
          Map.put(memo, {pow, i}, max(curr, memo[{pow, i + 1}]))

        !memo[{pow - 1, i + 1}] && !memo[{pow, i + 1}] ->
          memo

        !memo[{pow - 1, i + 1}] ->
          Map.put(memo, {pow, i}, memo[{pow, i + 1}])

        !memo[{pow, i + 1}] ->
          Map.put(memo, {pow, i}, curr + memo[{pow - 1, i + 1}])

        true ->
          Map.put(memo, {pow, i}, max(memo[{pow, i + 1}], curr + memo[{pow - 1, i + 1}]))
      end
    end
  end
end

Part 1

AoC2025.Day3.solve(input, 2)

Part 2

AoC2025.Day3.solve(input, 12)

By the way, input is like

[
  {9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1},
  {8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9},
  {2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 7, 8},
  {8, 1, 8, 1, 8, 1, 9, 1, 1, 1, 1, 2, 1, 1, 1}
] 
vkryukov

vkryukov

The algorithm is pretty straightforward: to take k largest batteries from a list of n, take the earliest largest digit among the first (n - k + 1), then iterate for all the digits after that one.

defmodule Y2025.Day03 do
  def digits(s) do
    s
    |> String.graphemes()
    |> Enum.map(&String.to_integer(&1))
  end

  def first_digit(digits, k) do
    n = length(digits)

    digits
    |> Enum.take(n - k + 1)
    |> Enum.with_index()
    |> Enum.max_by(fn {a, i} -> {a, -i} end)
  end

  def max_digits(digits, k) do
    k..1//-1
    |> Enum.reduce({digits, 0}, fn k, {digits, acc} ->
      {d, pos} = first_digit(digits, k)
      {digits |> Enum.drop(pos + 1), acc * 10 + d}
    end)
    |> elem(1)
  end

  def max_joltage(s, k \\ 2) do
    digits(s)
    |> max_digits(k)
  end

  def part1(s, k \\ 2) do
    s
    |> String.split("\n")
    |> Enum.map(&max_joltage(&1, k))
    |> Enum.sum()
  end

  def part2(s) do
    part1(s, 12)
  end
end
mudasobwa

mudasobwa

Creator of Cure

Brute force, after all.

  defmodule Day3 do
    @input "day3_1.input" |> File.read!() |> String.trim()

    @phase1 false

    if @phase1 do
      defp parse(<<d1::binary-size(1), d2::binary-size(1), rest::binary>>),
        do: do_parse_1(rest, {String.to_integer(d1), String.to_integer(d2)})

      defp do_parse("", {d1, d2}), do: d1 * 10 + d2

      defp do_parse(<<d::binary-size(1), rest::binary>>, {d1, d2}) do
        d = String.to_integer(d)

        acc =
          cond do
            d > d1 and rest != "" -> {d, -1}
            d > d2 -> {d1, d}
            true -> {d1, d2}
          end

        do_parse(rest, acc)
      end
    else
      import Aoc2025.H

      defp parse(<<d::binary-size(1), rest::binary>>),
        do: do_parse(rest, {String.to_integer(d), unquote_splicing(minus_ones(11))})

      defp do_parse("", digits),
        do: digits |> Tuple.to_list() |> Integer.undigits()

      defp do_parse(
             <<d::binary-size(1), rest::binary>>,
             {d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12}
           ) do
        d = String.to_integer(d)

        acc =
          cond do
            d > d1 and byte_size(rest) >= 11 -> {d, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
            d > d2 and byte_size(rest) >= 10 -> {d1, d, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
            d > d3 and byte_size(rest) >= 9 -> {d1, d2, d, -1, -1, -1, -1, -1, -1, -1, -1, -1}
            d > d4 and byte_size(rest) >= 8 -> {d1, d2, d3, d, -1, -1, -1, -1, -1, -1, -1, -1}
            d > d5 and byte_size(rest) >= 7 -> {d1, d2, d3, d4, d, -1, -1, -1, -1, -1, -1, -1}
            d > d6 and byte_size(rest) >= 6 -> {d1, d2, d3, d4, d5, d, -1, -1, -1, -1, -1, -1}
            d > d7 and byte_size(rest) >= 5 -> {d1, d2, d3, d4, d5, d6, d, -1, -1, -1, -1, -1}
            d > d8 and byte_size(rest) >= 4 -> {d1, d2, d3, d4, d5, d6, d7, d, -1, -1, -1, -1}
            d > d9 and byte_size(rest) >= 3 -> {d1, d2, d3, d4, d5, d6, d7, d8, d, -1, -1, -1}
            d > d10 and byte_size(rest) >= 2 -> {d1, d2, d3, d4, d5, d6, d7, d8, d9, d, -1, -1}
            d > d11 and byte_size(rest) >= 1 -> {d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d, -1}
            d > d12 -> {d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d}
            true -> {d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12}
          end

        do_parse(rest, acc)
      end
    end

    def calc(input \\ @input) do
      input
      |> String.split(["\s", "\n"], trim: true)
      |> Enum.sum_by(&parse/1)
    end
  end
sevenseacat

sevenseacat

Author of Ash Framework

This one was a bit of fun!

https://github.com/sevenseacat/advent_of_code/blob/main/lib/y2025/day03.ex

Name                     ips        average  deviation         median         99th %
day 03, part 1        787.83        1.27 ms     ±3.95%        1.25 ms        1.42 ms
day 03, part 2        623.54        1.60 ms     ±4.19%        1.58 ms        1.79 ms
hauleth

hauleth

Parse

batteries =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.map(fn row ->
    row
    |> String.to_charlist()
    |> Enum.map(& &1 - ?0)
  end)

Implementation

defmodule Joltage do
  def make_largest(list, n) do
    to_remove = length(list) - n
    Enum.reduce(1..to_remove, list, fn _, acc -> make_larger(acc) end)
  end
  
  def make_larger([_]), do: []
  def make_larger([a, b | rest]) when a < b, do: [b | rest]
  def make_larger([b | rest]), do: [b | make_larger(rest)]
end

Part 1

Enum.sum_by(batteries, &Integer.undigits(Joltage.make_largest(&1, 2)))

Part 2

Enum.sum_by(batteries, &Integer.undigits(Joltage.make_largest(&1, 12)))

All thanks to simple observation that to make number larger you need to drop first digit that is smaller than the next one or last digit. Now apply it N times to be left with just required amount of digits and you are good to go.

10
Post #6
rvnash

rvnash

Pretty much the same as most others.

Updated to simplify.

defmodule RAoc.Solutions.Y25.Day03 do
  alias AoC.Input

  def parse(input, _part) do
    Input.stream!(input, trim: true)
    |> Enum.map(fn line ->
      line
      |> String.split("", trim: true)
      |> Enum.map(&String.to_integer/1)
    end)
  end

  def part_one(problem) do
    problem
    |> Enum.sum_by(&highest_joltage(&1, 2))
  end

  def part_two(problem) do
    problem
    |> Enum.sum_by(&highest_joltage(&1, 12))
  end

  defp highest_joltage(list, num_batteries) do
    Enum.reduce(num_batteries..1//-1, {list, []}, fn on_battery, {list, digits} ->
      len = Enum.count(list)
      digit = Enum.take(list, len - (on_battery - 1)) |> Enum.max()
      {Enum.take(list, Enum.find_index(list, fn x -> x == digit end) + 1 - len), [digit | digits]}
    end)
    |> elem(1)
    |> Enum.reverse()
    |> Integer.undigits()
  end
end
everte

everte


  def p1(input) do
    input
    |> parse_input
    |> Enum.map(&find_jolt_for(&1, [], 2))
    |> Enum.sum()
  end

  # 168617068915447
  def p2(input) do
    input
    |> parse_input
    |> Enum.map(&find_jolt_for(&1, [], 12))
    |> Enum.sum()
  end

  defp find_jolt_for(_batteries, acc, 0), do: Enum.sum(acc)

  defp find_jolt_for(batteries, acc, count) do
    {batteries_to_search, _} = Enum.split(batteries, length(batteries) - count + 1)

    {biggest_jolt, index} =
      batteries_to_search
      |> Enum.with_index()
      |> Enum.max_by(fn {v, _} -> v end)

    {_, remainder} = Enum.split(batteries, index + 1)
    jolt = 10 ** (count - 1) * biggest_jolt

    find_jolt_for(remainder, [jolt | acc], count - 1)
  end

  defp parse_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      String.split(line, "", trim: true)
      |> Enum.map(&String.to_integer(&1))
    end)
  end

Not super happy with my solution today, fairly fiddly with the indexes and off-by-one’s. Will have to see in the topic what solutions work better :).

Enum.sum_by(batteries, &Integer.undigits(Joltage.make_largest(&1, 12)))

Oh! I didn’t know about this Integer.undigits, that helps!

DavidB

DavidB

defmodule Day3 do
  def file, do: Parser.read_file(3)
  def test, do: Parser.read_file("test")

  def parse(input) do
    Enum.map(input, fn batteries_bank ->
      batteries_bank
      |> String.graphemes()
      |> Enum.map(&String.to_integer/1)
    end)
  end

  def solve(input \\ file()) do
    input
    |> parse()
    |> Enum.map(&find_max(&1, 2))
    |> Enum.sum()
  end

  def solve_two(input \\ file()) do
    input
    |> parse()
    |> Enum.map(&find_max(&1, 12))
    |> Enum.sum()
  end

  def find_max(list, nb_digits) do
    list
    |> find_max_recursive(nb_digits)
    |> Integer.undigits()
  end

  def find_max_recursive(list, max_length, acc \\ [])
  def find_max_recursive(_list, 0, acc), do: acc

  def find_max_recursive(list, max_length, acc) do
    length = length(list)

    if length == max_length do
      acc ++ list
    else
      first =
        list
        |> Enum.take(length - max_length + 1)
        |> Enum.max()

      index = Enum.find_index(list, &(&1 == first))

      list
      |> Enum.split(index + 1)
      |> elem(1)
      |> find_max_recursive(max_length - 1, acc ++ [first])
    end
  end
end
tnlogy

tnlogy

I learn some more Enum-methods when reading your solutions. This is my 3:rd day using Elixir solution :). Updated using `Integer.undigits` after seeing it here.

defmodule Advent2025Test do
  use ExUnit.Case

  def day3_data() do
    "day3.txt"
    |> File.stream!()
    |> Enum.map(fn line ->
      String.trim(line) |> String.graphemes() |> Enum.map(&String.to_integer/1)
    end)
  end

  def max_index([x]) do
    {x, []}
  end

  def max_index([x | rest]) do
    {rx, ri} = max_index(rest)

    if x >= rx do
      {x, rest}
    else
      {rx, ri}
    end
  end

  def max_nl(l, 0) do
    {x, i} = max_index(l)
    {[x], i}
  end

  def max_nl(l, n) do
    {elements, last} = Enum.split(l, -n)
    {x, i} = max_index(elements)
    {x2, rest} = max_nl(i ++ last, n - 1)
    {[x] ++ x2, rest}
  end

  def max_n(l, n) do
    {digits, _} = max_nl(l, n)
    Integer.undigits(digits)
  end

  test "day3_p1" do
    sum = day3_data() |> Enum.map(&max_n(&1, 1)) |> Enum.sum()
    IO.puts("answer p1 #{inspect(sum)}")
  end

  test "day3_p2" do
    sum = day3_data() |> Enum.map(&max_n(&1, 11)) |> Enum.sum()
    IO.puts("answer p2 #{inspect(sum)}")
  end
end

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews