Powered by AppSignal & Oban Pro

Day 6 - Advent of Code 2025

lib/advent_of_code/2025/day-06.livemd

Day 6 - Advent of Code 2025

Mix.install([:kino, :benchee])

Links

Prompt

— Day 6: Trash Compactor —

After helping the Elves in the kitchen, you were taking a break and helping them re-enact a movie scene when you over-enthusiastically jumped into the garbage chute!

A brief fall later, you find yourself in a garbage smasher. Unfortunately, the door’s been magnetically sealed.

As you try to find a way out, you are approached by a family of cephalopods! They’re pretty sure they can get the door open, but it will take some time. While you wait, they’re curious if you can help the youngest cephalopod with her math homework.

Cephalopod math doesn’t look that different from normal math. The math worksheet (your puzzle input) consists of a list of problems; each problem has a group of numbers that need to be either added (+) or multiplied (*) together.

However, the problems are arranged a little strangely; they seem to be presented next to each other in a very long horizontal list. For example:

123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   +  

Each problem’s numbers are arranged vertically; at the bottom of the problem is the symbol for the operation that needs to be performed. Problems are separated by a full column of only spaces. The left/right alignment of numbers within each problem can be ignored.

So, this worksheet contains four problems:

  • 123 45 6 = 33210
  • 328 + 64 + 98 = 490
  • 51 387 215 = 4243455
  • 64 + 23 + 314 = 401

To check their work, cephalopod students are given the grand total of adding together all of the answers to the individual problems. In this worksheet, the grand total is 33210 + 490 + 4243455 + 401 =4277556.

Of course, the actual worksheet is much wider. You’ll need to make sure to unroll it completely so that you can read the problems clearly.

Solve the problems on the math worksheet. What is the grand total found by adding together all of the answers to the individual problems?

To begin, get your puzzle input.

— Part Two —

The big cephalopods come back to check on how things are going. When they see that your grand total doesn’t match the one expected by the worksheet, they realize they forgot to explain how to read cephalopod math.

Cephalopod math is written right-to-left in columns. Each number is given in its own column, with the most significant digit at the top and the least significant digit at the bottom. (Problems are still separated with a column consisting only of spaces, and the symbol at the bottom of the problem is still the operator to use.)

Here’s the example worksheet again:

123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   +  

Reading the problems right-to-left one column at a time, the problems are now quite different:

  • The rightmost problem is 4 + 431 + 623 = 1058
  • The second problem from the right is 175 581 32 = 3253600
  • The third problem from the right is 8 + 248 + 369 = 625
  • Finally, the leftmost problem is 356 24 1 = 8544

Now, the grand total is 1058 + 3253600 + 625 + 8544 = 3263827.

Solve the problems on the math worksheet again. What is the grand total found by adding together all of the answers to the individual problems?

Although it hasn’t changed, you can still get your puzzle input.

Input

input = Kino.Input.textarea("Please paste your input file:")
input = input |> Kino.Input.read()
"123 328  51 64 \n 45 64  387 23 \n  6 98  215 314\n*   +   *   +  "

Solution

defmodule Day06 do
  defdelegate parse(input, part), to: __MODULE__.Input

  def part1(input) do
    input
    |> parse("part1")
    |> Enum.sum_by(&do_problem/1)
  end

  def part2(input) do
    input
    |> parse("part2")
    |> Enum.map(&convert_column/1)
    |> build_problems(nil, [])
    |> Enum.sum_by(&do_problem/1)
  end

  defp do_problem(problem) when is_tuple(problem) do
    problem |> Tuple.to_list() |> do_problem()
  end

  defp do_problem(problem) do
    start_value = fn
      :+ -> 0
      :* -> 1
    end

    [operator | operands] = Enum.reverse(problem)

    Enum.reduce(
      operands,
      start_value.(operator),
      fn x, acc -> apply(Kernel, operator, [x, acc]) end
    )
  end

  defp convert_column(column) do
    column
    |> Tuple.to_list()
    |> Enum.reverse()
    |> normalize_column()
  end

  defp normalize_column([operator | digits]) when operator in [?+, ?*] do
    {operator, normalize_digits(digits)}
  end

  defp normalize_column([?\s | digits]) do
    if Enum.all?(digits, &(&1 == ?\s)),
      do: nil,
      else: normalize_digits(digits)
  end

  defp normalize_digits(digits) do
    digits
    |> Enum.reverse()
    |> to_string()
    |> String.replace(" ", "")
    |> String.to_integer()
  end

  defp build_problems([], current, all) do
    [current | all]
  end

  defp build_problems([nil | rest], current, all) do
    build_problems(rest, nil, [current | all])
  end

  defp build_problems([{?+, operand} | rest], nil, all) do
    build_problems(rest, [operand, :+], all)
  end

  defp build_problems([{?*, operand} | rest], nil, all) do
    build_problems(rest, [operand, :*], all)
  end

  defp build_problems([operand | rest], current, all) do
    build_problems(rest, [operand | current], all)
  end

  defmodule Input do
    def parse(input, part) when is_binary(input) do
      input
      |> String.splitter("\n", trim: true)
      |> parse(part)
    end

    def parse(input, "part1") do
      input
      |> Stream.map(fn line ->
        line
        |> String.split(" ", trim: true)
        |> Enum.map(fn
          "+" -> :+
          "*" -> :*
          i -> String.to_integer(i)
        end)
      end)
      |> Enum.zip()
    end

    def parse(input, "part2") do
      input
      |> Stream.map(&String.to_charlist/1)
      |> Enum.zip()
    end
  end
end
{:module, Day06, <<70, 79, 82, 49, 0, 0, 18, ...>>, ...}

Solve the problems on the math worksheet. What is the grand total found by adding together all of the answers to the individual problems?

Your puzzle answer was 5060053676136.

Day06.part1(input)
4277556

Solve the problems on the math worksheet again. What is the grand total found by adding together all of the answers to the individual problems?

Your puzzle answer was 9695042567249.

Day06.part2(input)
3263827

Both parts of this puzzle are complete! They provide two gold stars: **

At this point, you should return to your Advent calendar and try another puzzle.

If you still want to see it, you can get your puzzle input.

Tests

ExUnit.start(auto_run: false)

defmodule Day06Test do
  use ExUnit.Case, async: false

  setup_all do
    [
      input: "123 328  51 64 \n 45 64  387 23 \n  6 98  215 314\n*   +   *   +  "
    ]
  end

  describe "part1/1" do
    test "returns expected value", %{input: input} do
      assert Day06.part1(input) == 4277556
    end
  end

  describe "part2/1" do
    test "returns expected value", %{input: input} do
      assert Day06.part2(input) == 3263827
    end
  end
end

ExUnit.run()
Running ExUnit with seed: 464124, max_cases: 16

..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures
%{total: 2, failures: 0, excluded: 0, skipped: 0}

Benchmarking

defmodule Benchmarking do
  # https://github.com/bencheeorg/benchee
  def run(input) do
    Benchee.run(
      %{
        "Part 1" => fn -> Day06.part1(input) end,
        "Part 2" => fn -> Day06.part2(input) end
      },
      memory_time: 2,
      reduction_time: 2
    )

    nil
  end
end
{:module, Benchmarking, <<70, 79, 82, 49, 0, 0, 8, ...>>, ...}
Benchmarking.run(input)
Operating System: macOS
CPU Information: Apple M1
Number of Available Cores: 8
Available memory: 8 GB
Elixir 1.19.4
Erlang 28.2
JIT enabled: true

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 2 s
reduction time: 2 s
parallel: 1
inputs: none specified
Estimated total run time: 22 s
Excluding outliers: false

Benchmarking Part 1 ...
Benchmarking Part 2 ...
Calculating statistics...
Formatting results...

Name             ips        average  deviation         median         99th %
Part 1        1.60 K        0.62 ms    ±12.52%        0.62 ms        0.73 ms
Part 2        0.58 K        1.72 ms     ±7.10%        1.72 ms        1.88 ms

Comparison: 
Part 1        1.60 K
Part 2        0.58 K - 2.76x slower +1.10 ms

Memory usage statistics:

Name      Memory usage
Part 1         1.62 MB
Part 2         5.49 MB - 3.39x memory usage +3.87 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name   Reduction count
Part 1        125.36 K
Part 2        421.67 K - 3.36x reduction count +296.31 K

**All measurements for reduction count were the same**
nil