bjorng

bjorng

Erlang Core Team

Advent of Code 2025 - Day 6

defmodule Day06 do
  def part1(input) do
    input
    |> Enum.map(fn line ->
      line
      |> String.split(" ", trim: true)
      |> Enum.map(fn token ->
        case Integer.parse(token) do
          :error -> String.to_atom(token)
          {n, ""} -> n
        end
      end)
    end)
    |> transpose
    |> Enum.map(fn column ->
      operator = List.last(column)
      Enum.drop(column, -1)
      |> Enum.reduce(&(eval(operator, &1, &2)))
    end)
    |> Enum.sum
  end

  def part2(input) do
    operators = List.last(input)
    spaces = count_spaces(String.to_charlist(operators))
    operators = operators
    |> String.to_charlist
    |> Enum.filter(&(&1 !== ?\s))
    |> Enum.map(&List.to_atom([&1]))

    input
    |> Enum.drop(-1)
    |> Enum.map(fn line ->
      String.to_charlist(line)
      |> split_line(spaces)
    end)
    |> transpose
    |> Enum.zip(operators)
    |> Enum.map(fn {column, operator} ->
      column
      |> transpose
      |> Enum.map(fn column ->
        column
        |> Enum.filter(&(&1 !== ?\s))
        |> List.to_integer
      end)
      |> Enum.reduce(&(eval(operator, &1, &2)))
    end)
    |> Enum.sum
  end

  defp split_line([], []), do: []
  defp split_line(line, [n]) do
    {number, []} = Enum.split(line, n + 1)
    [number]
  end
  defp split_line(line, [n | ns]) do
    {number, rest} = Enum.split(line, n)
    [?\s | rest] = rest
    [number | split_line(rest, ns)]
  end

  defp count_spaces([]), do: []
  defp count_spaces([_op|rest]) do
    {spaces, rest} = Enum.split_while(rest, &(&1 === ?\s))
    [length(spaces) | count_spaces(rest)]
  end

  defp eval(operator, number1, number2) do
    apply(Kernel, operator, [number1, number2])
  end

  defp transpose(list) do
    Enum.zip_with(list, &Function.identity/1)
  end
end

First 10 of 21 Posts Switch mode

Aetherus

Aetherus

Yet another easy puzzle.

Part 1

puzzle_input
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, " ", trim: true))
|> Enum.map(fn row ->
  Enum.map(row, fn
    "+" -> &+/2
    "*" -> &*/2
    s -> String.to_integer(s)
  end)
end)
|> Enum.zip_with(&Function.identity/1)
|> Enum.map(&Enum.reverse/1)
|> Enum.sum_by(fn [operator | numbers] ->
  Enum.reduce(numbers, operator)
end)

Part 2

Split the puzzle input into [[char()]], then rotate left (= transpose then reverse).

puzzle_input
|> String.split("\n", trim: true)
|> Enum.map(&:binary.bin_to_list/1)
|> Enum.zip_with(&Function.identity/1)
|> Enum.reverse()
|> Enum.map(&to_string/1)
|> Enum.map(&String.trim/1)
|> Enum.map(&Integer.parse/1)
|> Enum.flat_map(fn
  :error ->
    []
  
  {num, ""} ->
    [num]

  {num, op} ->
    op
    |> String.trim()
    |> String.to_atom()
    |> then(fn op -> &apply(Kernel, op, [&1, &2]) end)
    |> then(&[num, &1])
end)
|> Enum.chunk_while([],
  fn
    num, acc when is_integer(num) -> {:cont, [num | acc]}
    op, acc -> {:cont, Enum.reduce(acc, op), []}
  end,
  fn
    [] -> {:cont, []}
  end
)
|> Enum.sum()
vkryukov

vkryukov

I’m using my Array module again: Array.col returns a column as a list, Array.subarray returns a subarray with given indexes, and Array.from_list is a constructor that takes a list of lists.

defmodule Y2025.Day06 do
  def parse_lines(s), do: s |> String.trim() |> String.split("\n") |> Enum.split(-1)
  def split_by_spaces(s), do: String.trim(s) |> String.split(~r/\s+/)

  def parse1(s) do
    {nums, [ops]} = parse_lines(s)

    nums =
      nums
      |> Enum.map(fn line ->
        line |> split_by_spaces() |> Enum.map(&String.to_integer/1)
      end)
      |> Array.from_list()

    ops = ops |> split_by_spaces()

    {nums, ops}
  end

  def part1(s) do
    {nums, ops} = parse1(s)

    ops
    |> Enum.with_index()
    |> Enum.map(fn {op, i} -> apply_op(op, Array.col(nums, i)) end)
    |> Enum.sum()
  end

  def apply_op("+", nums), do: Enum.sum(nums)
  def apply_op("*", nums), do: Enum.product(nums)

  def parse2(s) do
    {nums, [ops]} = parse_lines(s)

    nums =
      nums
      |> Enum.map(fn line -> String.graphemes(line) |> Enum.map(&maybe_to_digit/1) end)
      |> Array.from_list()

    ops = ops |> split_by_spaces()

    {nums, ops}
  end

  def maybe_to_digit(" "), do: " "
  def maybe_to_digit(d), do: String.to_integer(d)

  def part2(s) do
    {nums, ops} = parse2(s)

    nums =
      nums
      |> array_split_by_spaces()
      |> Enum.map(fn a -> cellophod_digits(a) end)

    Enum.zip(ops, nums)
    |> Enum.map(fn {op, num} -> if op == "*", do: Enum.product(num), else: Enum.sum(num) end)
    |> Enum.sum()
  end

  @doc "Split array of digits and spaces into a list of arrays separated by columns of spaces"
  def array_split_by_spaces(%Array{} = a) do
    do_array_split_by_spaces(a, 0, 0, [])
  end

  def do_array_split_by_spaces(a, i, j, acc) do
    cond do
      i >= a.x_max ->
        Enum.reverse(acc)

      j == a.x_max || all_spaces?(Array.col(a, j)) ->
        do_array_split_by_spaces(a, j + 1, j + 1, [Array.subarray(a, x: i..(j - 1)) | acc])

      true ->
        do_array_split_by_spaces(a, i, j + 1, acc)
    end
  end

  def all_spaces?(list), do: Enum.all?(list, &(&1 == " "))

  def cellophod_digits(%Array{} = a) do
    0..(a.x_max - 1)
    |> Enum.map(fn i -> Array.col(a, i) |> Enum.reject(&(&1 == " ")) end)
    |> Enum.map(&Integer.undigits/1)
  end
end
lkuty

lkuty

#!/usr/bin/env elixir

# Advent of Code 2025. Day 6

defmodule M do
  def i("*"), do: "*"
  def i("+"), do: "+"
  def i(x), do: String.to_integer(x)

  def n(a,b,c,d) do
    [a,b,c,d]
    |> Enum.reject(& &1 == " ")
    |> Enum.map(&String.to_integer/1)
    |> Integer.undigits()
  end
end

# Part 1
# Original solution, before knowing part 2

rows = File.read!("../day06.txt") |> String.trim_trailing() |> String.split("\n")
map = rows |> Enum.reduce(%{}, fn row, map ->
  row
  |> String.trim_trailing()
  |> String.split(~r/\s+/)
  |> Enum.with_index()
  |> Enum.reduce(map, fn {cell, col_idx}, map ->
    Map.update(
      map,
      col_idx,
      [M.i(cell)],
      # We could as well do the operations directly here and only store the result in the map at the given col_idx
      fn lst -> [(case cell do "*" -> &Enum.product/1 ; "+" -> &Enum.sum/1 ; _ -> M.i(cell) end) | lst] end
      # variant 1
      # fn lst -> [(case cell do "*" -> {1, fn a,b -> a*b end} ; "+" -> {0, fn a,b -> a+b end} ; _ -> M.i(cell) end) | lst] end
    )
  end)
end)

map
|> Enum.map(fn {_idx, [fun | numbers]} -> fun.(numbers) end)
# variant 1
# |> Enum.map(fn {_idx, [{id, fun} | numbers]} -> Enum.reduce(numbers, id, fun) end)
|> Enum.sum()
|> IO.inspect(label: "Day 6. Part 1")

# Part 2

File.read!("../day06.txt")
|> String.trim_trailing("\n")
|> String.split("\n")
|> Enum.map(&String.codepoints/1)
|> Enum.zip() # transpose the file
|> Enum.reduce({0, [], nil}, fn
     {" ", " ", " ", " ", " "}, {total, numbers, "+"} -> {total+Enum.sum(numbers), [], nil}
     {" ", " ", " ", " ", " "}, {total, numbers, "*"} -> {total+Enum.product(numbers), [], nil}
     {a,b,c,d," "}, {total, numbers, op} -> {total, [M.n(a,b,c,d) | numbers], op}
     {a,b,c,d,op}, {total, numbers, nil} -> {total, [M.n(a,b,c,d) | numbers], op}
   end)
|> then(fn
     {total,numbers,"+"} -> total+Enum.sum(numbers)
     {total,numbers,"*"} -> total+Enum.product(numbers)
   end)
|> IO.inspect(label: "Day 6. Part 2")

# Part 1 revisited

File.read!("../day06.txt")
|> String.trim_trailing("\n")
|> String.split("\n")
|> Enum.map(fn row -> row |> String.trim_trailing() |> String.split(~r/\s+/) |> Enum.map(&M.i/1) end)
|> Enum.zip() # transpose the file
|> Enum.reduce(0, fn
     {a,b,c,d,"+"}, total -> total+a+b+c+d
     {a,b,c,d,"*"}, total -> total+a*b*c*d
   end)
|> IO.inspect(label: "Day 6. Part 1")
hauleth

hauleth

Setup

{tasks, [ops]} =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.split(-1)

ops =
  ops
  |> String.split()
  |> Enum.map(&String.to_atom/1)

Part 1

tasks
|> Enum.map(&String.split/1)
|> Enum.zip_with(fn numbers -> Enum.map(numbers, &String.to_integer/1) end)
|> Enum.zip(ops)
|> Enum.sum_by(fn
  {nums, :+} -> Enum.sum(nums)
  {nums, :*} -> Enum.product(nums)
end)

Part 2

tasks
|> Enum.map(&String.to_charlist/1)
|> Enum.zip_with(&(&1 |> List.to_string() |> String.trim()))
|> Enum.chunk_while(
  [],
  fn
    "", acc -> {:cont, acc, []}
    num, acc -> {:cont, [String.to_integer(num) | acc]}
  end,
  &{:cont, &1, []}
)
|> Enum.zip(ops)
|> Enum.sum_by(fn
  {nums, :+} -> Enum.sum(nums)
  {nums, :*} -> Enum.product(nums)
end)
lud

lud

Arf I wish I had thought to use String.to_integer before chunking!

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

  # No separate parsing today :)
  def parse(input, _), do: input

  def part_one(input) do
    input
    |> Input.stream!(trim: true)
    |> Stream.map(&String.split(&1, " ", trim: true))
    |> Enum.zip_with(& &1)
    |> Enum.sum_by(fn list ->
      {op, args} = List.pop_at(list, -1)
      apply_op(op, Enum.map(args, &String.to_integer/1))
    end)
  end

  def part_two(input) do
    rows = Input.read!(input) |> String.split("\n", trim: true)
    {ops, rows} = List.pop_at(rows, -1)
    rows = Enum.map(rows, &String.graphemes/1)
    ops = String.split(ops, " ", trim: true)

    rows
    |> Stream.unfold(fn
      [[] | _] ->
        nil

      rows ->
        {rows, col} = Enum.map_reduce(rows, [], fn [h | t], acc -> {t, [h | acc]} end)
        {:lists.reverse(col), rows}
    end)
    |> Stream.map(fn str_digits ->
      case str_digits |> Enum.join("") |> String.trim() do
        "" -> nil
        str_num -> String.to_integer(str_num)
      end
    end)
    |> Stream.chunk_by(&is_integer/1)
    |> Stream.filter(&(&1 != [nil]))
    |> Stream.zip(ops)
    |> Enum.sum_by(fn {numbers, op} -> apply_op(op, numbers) end)
  end

  defp apply_op("+", args), do: Enum.sum(args)
  defp apply_op("*", args), do: Enum.product(args)
end

Edit:

yes, that makes is much simpler:

  def part_two(input) do
    rows = Input.read!(input) |> String.split("\n", trim: true)
    {ops, rows} = List.pop_at(rows, -1)
    rows = Enum.map(rows, &String.graphemes/1)
    ops = String.split(ops, " ", trim: true)

    rows
    |> Enum.zip_with(fn digits -> String.trim(Enum.join(digits)) end)
    |> Stream.map(fn
      "" -> nil
      str_num -> String.to_integer(str_num)
    end)
    |> Stream.chunk_by(&is_integer/1)
    |> Stream.filter(&(&1 != [nil]))
    |> Stream.zip(ops)
    |> Enum.sum_by(fn {numbers, op} -> apply_op(op, numbers) end)
  end

I had a version of my first solution with chunk_while as well but I feel it makes everything harder to read.

sevenseacat

sevenseacat

Author of Ash Framework

I don’t love the code I wrote today - all the work was in parsing the input into the shape I wanted. Lots of string manipulations, list transposing, and messy stuff.

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

Also, this is the first time this year I’ve been confused at the speed of my solution, did some profiling, and found out that all the time was totally not where I expected it to be!

Name                     ips        average  deviation         median         99th %
day 06, part 1        363.34        2.75 ms     ±1.88%        2.76 ms        2.82 ms
day 06, part 2        258.39        3.87 ms     ±5.48%        3.91 ms        4.97 ms
dompdv

dompdv

Same comment. Most of the job is done in the parsing, accumulate data in Map, to get a data structure that makes the main algorithm I simple.
But I still wonder how to make it nice.


defmodule AdventOfCode.Solution.Year2025.Day06 do
  import Enum, only: [with_index: 1, reduce: 3, sum: 1, take: 2]

  def mul(l), do: reduce(l, 1, &(&1 * &2))

  def parse_part1(input) do
    # Returns a list of groups like {:mul, [12,1453,23]} (operator, operands)
    input
    |> String.split("\n", trim: true)
    |> reduce(
      %{},
      fn line, acc ->
        indexed_groups = line |> String.split(" ", trim: true) |> with_index()

        reduce(indexed_groups, acc, fn {chunk, chunk_number}, local_acc ->
          {operator, operands} = Map.get(local_acc, chunk_number, {nil, []})

          case chunk do
            "*" ->
              Map.put(local_acc, chunk_number, {:mul, operands})

            "+" ->
              Map.put(local_acc, chunk_number, {:add, operands})

            number ->
              Map.put(local_acc, chunk_number, {operator, [String.to_integer(number) | operands]})
          end
        end)
      end
    )
    |> Map.values()
  end

  def part1(input) do
    parse_part1(input)
    |> reduce(0, fn
      {op, l}, acc ->
        acc + if op == :mul, do: mul(l), else: sum(l)
    end)
  end

  def parse2(input) do
    lines = input |> String.split("\n", trim: true)

    {
      # map of %{column => number}
      reduce(take(lines, length(lines) - 1), %{}, fn line, acc ->
        line
        |> String.codepoints()
        |> with_index()
        |> reduce(acc, fn {c, col}, local_acc ->
          Map.update(local_acc, col, c, &(&1 <> c))
        end)
      end)
      |> reduce(%{}, fn {col, s}, acc ->
        s = String.trim(s)
        if s == "", do: acc, else: Map.put(acc, col, String.to_integer(s))
      end)
      |> Map.new(),
      # map of %{column => operator}
      List.last(lines)
      |> to_charlist()
      |> with_index()
      |> reduce(%{}, fn {c, col}, acc ->
        if c == 32, do: acc, else: Map.put(acc, col, c)
      end)
    }
  end

  # Find the next element in a list. returns max_length if reaching the end of the list
  def next([val], val, max_length), do: max_length
  def next([val, n | _], val, _max_length), do: n
  def next([_n | r], val, max_length), do: next(r, val, max_length)

  # Gather numbers starting from col till an empty column
  def gather(numbers, col, acc) do
    if Map.has_key?(numbers, col), do: gather(numbers, col + 1, [numbers[col] | acc]), else: acc
  end

  def part2(input) do
    {numbers, operators} = parse2(input)

    reduce(operators, 0, fn {col, op}, acc ->
      l = gather(numbers, col, [])
      acc + if op == ?*, do: mul(l), else: sum(l)
    end)
  end
end
rvnash

rvnash

Anyone else feel there was little “fun” in todays problem? I enjoy problems when there is a challenging data structure or algorithmic obstacle to surmount. This seemed like a lot of persnickety busywork.

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

  # Parts 1 and 2 have their own parsers
  def parse(input, _part) do
    input
  end

  def part_one(input) do
    Input.stream!(input, trim: true)
    |> Stream.map(fn line ->
      String.split(line, " ", trim: true)
    end)
    |> Enum.zip()
    |> Enum.map(fn tuple ->
      [operator | operands] = Tuple.to_list(tuple) |> Enum.reverse()
      {operator, Enum.map(operands, &String.to_integer/1)}
    end)
    |> Enum.map(&solve/1)
    |> Enum.sum()
  end

  def part_two(input) do
    Input.read!(input)
    |> String.split("\n", trim: true)
    |> Enum.map(&String.graphemes/1)
    |> move_last_to_first()
    |> then(fn [operators | operands] ->
      operators =
        operators
        |> Enum.reject(&(&1 == " "))
        |> Enum.reverse()

      operands =
        operands
        |> Enum.zip()
        |> Enum.reverse()
        |> Enum.map(fn tuple ->
          tuple
          |> Tuple.to_list()
          |> Enum.join()
          |> String.trim()
        end)
        |> Enum.chunk_by(&(&1 == ""))
        |> Enum.reject(&(&1 == [""]))
        |> Enum.map(fn list -> Enum.map(list, &String.to_integer/1) end)

      Enum.zip(operators, operands)
    end)
    |> Enum.map(&solve/1)
    |> Enum.sum()
  end

  defp solve({"*", operands}), do: Enum.product(operands)
  defp solve({"+", operands}), do: Enum.sum(operands)
  defp move_last_to_first([]), do: []

  defp move_last_to_first(list) do
    [last | rest_reversed] = Enum.reverse(list)
    [last | Enum.reverse(rest_reversed)]
  end
end
BartOtten

BartOtten

Yeah, as I have a time limit I only made Part 1, Part 2 I only drafted in pseudo code: taking largest number in a row (A), then chunk the 'row string’ with number A to have the numbers including whitespace. From there on it’s just ‘grid’ing again.

Part 1

  def parse(input, _part) do
    Input.read!(input)
    |> String.trim()
    |> String.split("\n")
  end

  def part_one(problem) do
    problem
    |> Enum.flat_map(&String.split(&1) |> Enum.with_index )
    |> Enum.reverse()
    |> Enum.group_by(fn {x,y} -> y end, fn {x,y} -> x end)
    |> Enum.map(fn {_, [op | rest]} -> 
        Enum.intersperse(rest, op) 
        |> List.to_string() 
        |> Code.eval_string() 
        |> elem(0) 
       end)
    |> Enum.sum()
  end

BartOtten

BartOtten

Reading the second problem on the iPhone makes it more challenging to understand how their math works… (see the alignment of numbers)

Where Next?

Trending in Challenges Top

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement