Powered by AppSignal & Oban Pro

Advent of code 2025 - Day 6

day06.livemd

Advent of code 2025 - Day 6

Description

Day 6 - Trash Compactor

defmodule Load do
  def input do
    File.read!("#{__DIR__}/inputs/day06.txt")
  end
end
defmodule Day6 do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&String.split(&1, " ", trim: true))
  end

  def part1(input) do
    parse(input)
    |> Enum.zip()
    |> Enum.map(&Tuple.to_list/1)
    |> Enum.map(fn list ->
      [List.last(list) | List.delete_at(list, length(list) - 1) |> Enum.map(&String.to_integer/1)]
    end)
    |> Enum.reduce(0, fn expression, sum ->
      sum + calculate(expression)
    end)
  end

  def calculate(expression) do
    {_op, sum} =
      expression
      |> Enum.reduce({nil, nil}, fn item, {op, sum} ->
        if is_nil(op) do
          {item, nil}
        else
          if is_nil(sum) do
            {op, item}
          else
            apply_operator({op, sum}, item)
          end
        end
      end)

    sum
  end

  def apply_operator({op, sum}, item) do
    case op do
      "*" -> {op, sum * item}
      "+" -> {op, sum + item}
      _ -> raise "invalid operator " <> op
    end
  end

  def part2(input) do
    lines = String.split(input, "\n", trim: true)
    {number_lines, [operator_line]} = Enum.split(lines, -1)

    num_columns = Enum.count(String.split(operator_line, " ", trim: true))

    col_widths =
      number_lines
      |> Enum.map(&amp;String.split(&amp;1, " ", trim: true))
      |> Enum.zip()
      |> Enum.map(fn col_tuple ->
        col_tuple |> Tuple.to_list() |> Enum.map(&amp;String.length/1) |> Enum.max()
      end)

    col_boundaries =
      col_widths
      |> Enum.scan(0, fn width, acc -> acc + width + 1 end)
      |> then(&amp;[0 | &amp;1])

    padded_lines =
      number_lines
      |> Enum.map(fn line ->
        line
        |> String.graphemes()
        |> Enum.with_index()
        |> Enum.map(fn {char, i} ->
          is_separator = Enum.member?(col_boundaries, i + 1)
          if not is_separator and char == " ", do: "-", else: char
        end)
        |> Enum.join()
      end)

    actual_numbers =
      padded_lines
      |> Enum.map(fn line ->
        line |> String.reverse() |> String.split(" ")
      end)
      |> List.flatten()
      |> Enum.chunk_every(num_columns)
      |> Enum.zip()
      |> Enum.map(&amp;Tuple.to_list/1)
      |> Enum.map(fn inner_list ->
        inner_list
        |> Enum.map(&amp;String.graphemes/1)
        |> Enum.zip()
        |> Enum.map(fn tuple -> Tuple.to_list(tuple) |> Enum.join() end)
        |> Enum.map(fn item -> String.replace(item, "-", "") |> String.to_integer() end)
      end)

    operators = operator_line |> String.split() |> Enum.reverse()

    {expressions, _} =
      actual_numbers
      |> Enum.reduce({[], operators}, fn expression, {expressions, [operator | operators]} ->
        {[[operator | expression] | expressions], operators}
      end)

    expressions
    |> Enum.reduce(0, fn expression, sum -> calculate(expression) + sum end)
  end
end
ExUnit.start(autorun: false)

defmodule Test do
  use ExUnit.Case, async: true

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

  test "part 1" do
    assert Day6.part1(@input) == 4_277_556
  end

  test "part 2" do
    assert Day6.part2(@input) == 3_263_827
  end
end

ExUnit.run()
Day6.part1(Load.input())
Day6.part2(Load.input())