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

1 Like

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()

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
#!/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")

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)
3 Likes

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.

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.

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

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

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
1 Like

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

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

I don’t use elixir much, mainly just learning for some hobby projects I have in mind, but I like doing these puzzles as I learn about functions I don’t use or know much and it’s just a different way of thinking stringing things together in pipelines. I kind of enjoyed the parsing challenge in the second part. Zip and chunk_by are ones I rarely use that helped me out today.

defmodule Puzzle do
  def parse_first(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&String.split(&1, " ", trim: true))
    |> Enum.zip()
    |> Enum.map(&Tuple.to_list/1)
    |> Enum.map(&Enum.reverse/1)
  end

  def parse_second(input) do
    rows = String.split(input, "\n")

    operators = 
      List.last(rows)
      |> String.split(" ", trim: true)
        
    rows =
      rows
      |> List.delete_at(length(rows) - 1)
      |> Enum.map(&String.graphemes/1)
      |> Enum.zip()
      |> Enum.map(&Tuple.to_list/1)
      |> Enum.map(&Enum.join/1)
      |> Enum.map(&String.trim/1)
      |> Enum.chunk_by(&(&1 == ""))
      |> Enum.filter(fn chunk -> chunk != [""] end)
      
    Enum.zip(operators, rows)
    |> Enum.map(fn {operator, values} -> [operator | values] end)  
  end

  def compute_column([operator | values]) do
    case operator do
      "*" ->
        Enum.reduce(values, 1, fn value_str, acc ->
          String.to_integer(value_str) * acc
        end)

      "+" ->
        Enum.reduce(values, 0, fn value_str, acc ->
          String.to_integer(value_str) + acc
        end)
    end
  end
end

#Part 1
Puzzle.parse_first(input)
|> Enum.reduce(0, fn col, acc ->
  acc + Puzzle.compute_column(col)
end)

#Part 2
Puzzle.parse_second(input)
|> Enum.reduce(0, fn col, acc ->
  acc + Puzzle.compute_column(col)
end)

Today was a very easy but nice problem

That one is going straight into my Aoc.Utils, thank you!

4 Likes

2025 Dec 06

Trash Compactor

defmodule Cephalopod do
  def parse_line(line, columns) do
    col_strs = String.split(line)
    columns = if columns == nil do
      Enum.map(col_strs, fn _ -> [] end)
    else
      columns
    end
    Enum.zip_with(col_strs, columns, fn str, column ->
      case Integer.parse(str) do
        :error ->
          [String.to_atom(str) | column]
        {n, ""} ->
          [n | column]
      end
    end)
  end

  def parse(lines) do
    Enum.reduce(lines, nil, &parse_line/2)
  end

  def sum_work(columns) do
    Enum.reduce(columns, 0, fn column, sum -> 
      case column do
        [:+ | ns] ->
          sum + Enum.sum(ns)
        [:* | ns] ->
          sum + Enum.product(ns)
      end
    end)
  end
end
test_input = """
123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   +  
""" |> String.split("\n", trim: true)
  |> IO.inspect()
test1_work = Cephalopod.parse(test_input)
Cephalopod.sum_work(test1_work)
input_work = File.stream!(__DIR__ <> "/dec-06-input.txt")
  |> Cephalopod.parse()
Cephalopod.sum_work(input_work)

Part Two

defmodule Cephalopod2 do
  def parse(lines) do
    flipped = Enum.map(lines, fn line -> 
      to_charlist(String.trim_trailing(line, "\n"))
    end)
      |> Enum.zip_with(&Function.identity/1)
      |> Enum.map(&to_string/1)
    Enum.flat_map(flipped, fn s ->
      case Regex.run(~r"^\s*(\d*)\s*([*+])?$", s) do
        [_match, ""] ->
          []
        [_match, digits] ->
          [String.to_integer(digits)]
        [_match, digits, op] ->
          [String.to_atom(op), String.to_integer(digits)]
      end
    end)
  end

  def sum_work(formulae, op \\ nil, acc \\ nil, sum \\ 0) do
    case formulae do
      [:+ | rest] ->
        sum = if acc != nil do
          sum + acc
        else
          sum
        end
        sum_work(rest, :+, 0, sum)
      [:* | rest] ->
        sum = if acc != nil do
          sum + acc
        else
          sum
        end
        sum_work(rest, :*, 1, sum)
      [n | rest] ->
        acc = case op do
          :* ->
            n * acc
          :+ ->
            n + acc
        end
        sum_work(rest, op, acc, sum)
      [] ->
        sum + acc
    end
  end
end
test2_work = Cephalopod2.parse(test_input)
  |> IO.inspect()
Cephalopod2.sum_work(test2_work)
input_work2 = File.stream!(__DIR__ <> "/dec-06-input.txt")
  |> Cephalopod2.parse()
  |> IO.inspect()
Cephalopod2.sum_work(input_work2)

Didn’t know about that one, would have made my life easier

Enum.zip_with(list, &Function.identity/1)

My original code, didn’t refactor, so not very readable :

defmodule Day6 do
  def file, do: Parser.read_file(6)
  def test, do: Parser.read_file("test")

  def parse(input) do
    input
  end

  def solve(input \\ file()) do
    input
    |> Enum.reduce(%{}, fn line, acc ->
      line
      |> String.split()
      |> Stream.with_index()
      |> Enum.reduce(acc, fn {value, index}, acc ->
        Map.update(acc, index, [value], fn list -> [value | list] end)
      end)
    end)
    |> Map.values()
    |> Enum.map(&solve_line/1)
    |> Enum.sum()
  end

  def solve_line(list) do
    [operation | rest] = list
    new_list = Enum.reverse(rest) |> Enum.map(&String.to_integer/1)
    calculate(new_list, operation)
  end

  def calculate(list, "+"), do: Enum.sum(list)
  def calculate(list, "*"), do: Enum.product(list)

  def solve_two(input \\ file()) do
    {operator_line, number_lines} = Enum.map(input, &String.graphemes/1) |> List.pop_at(-1)

    operator_line
    |> get_operation_length
    |> Enum.reduce({[], number_lines}, fn {operator, operation_length},
                                          {new_stuf, lines_to_cut} ->
      {problem, rest} =
        Enum.reduce(lines_to_cut, {[], []}, fn line, {new, old} ->
          {one, two} = line |> Enum.split(operation_length)
          {new ++ [one], old ++ [two]}
        end)

      {[{operator, problem} | new_stuf], rest}
    end)
    |> elem(0)
    |> Enum.map(fn {operator, future_number} ->
      future_number |> build_number() |> Enum.reverse() |> calculate(operator)
    end)
    |> Enum.sum()
  end

  def build_number(list) do
    list
    |> Enum.reduce(%{}, fn list, acc ->
      list
      |> Stream.with_index()
      |> Map.new(fn {k, v} -> {v, k} end)
      |> Map.merge(acc, fn _key, v1, v2 -> [v1 | List.wrap(v2)] end)
    end)
    |> Map.values()
    |> Enum.map(fn list ->
      list
      |> Enum.join()
      |> String.trim()
      |> String.reverse()
    end)
    |> Enum.reject(&(&1 == ""))
    |> Enum.map(&String.to_integer/1)
  end

  def get_operation_length(operator_line) do
    operator_line
    |> Enum.reduce([], fn symbol, acc ->
      case symbol do
        " " ->
          [{operator, count} | rest] = acc
          [{operator, count + 1} | rest]

        operator ->
          [{operator, 1} | acc]
      end
    end)
    |> Enum.map(fn {op, count} -> {op, count} end)
    |> Enum.reverse()
  end
end

Apologies for those who recurse.

I love Elixir but I’m solving the problems in Go this year. I also felt day06 was a fussy problem not challenging. I parsed part 1 quickly and solved it. Then it was parse again with a rotated input. Ok, how do you rotate a grid, then apply use in the puzzle. That was about the only interesting bit.

My Go solution is here if anyone wants to look.

Part 1

transpose = fn list -> Enum.zip_with(list, &Function.identity/1) end

values =
  File.read!("ids.txt")
  |> String.split("\n")
  |> Enum.map(&(Regex.split(~r{\s+}, &1, trim: true)))

transpose.(values)
|> Enum.map(&Enum.reverse/1)
|> Enum.map(fn [head | tail] ->
  case head do
    "*" -> tail |> Enum.map(&String.to_integer/1) |> Enum.product
    "+" -> tail |> Enum.map(&String.to_integer/1) |> Enum.sum
  end
end)
|> Enum.sum
|> IO.inspect

Part 2

transpose = fn list -> Enum.zip_with(list, &Function.identity/1) end

items =
  File.read!("ids.txt")
  |> String.split("\n")
  |> Enum.map(&(String.split(&1, "", trim: true)))

transpose.(items)
|> Enum.map(&Enum.reverse/1)
|> Enum.map(fn [head | tail] -> {head, tail |> Enum.reverse |> Enum.join("") |> String.trim} end)
|> Enum.chunk_by(fn {_, value} -> value == "" end)
|> Enum.reject(&(&1 == [{" ", ""}]))
|> Enum.map(fn [{operand, value} | tail] ->
  Enum.reduce(tail, String.to_integer(value), fn {_, x}, acc ->
    case operand do
      "*" -> acc * String.to_integer(x)
      "+" -> acc + String.to_integer(x)
    end
  end)
end)
|> Enum.sum
|> IO.inspect

You can replace

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

with

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

shorter and easier to read :slight_smile:

1 Like

Enum.product(list)

3 Likes