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 21 to 12

billylanchantin

billylanchantin

defmodule Day03 do
  def part1(file), do: main(file, 2)
  def part2(file), do: main(file, 12)

  def main(file, n) do
    file
    |> Util.file_to_lists_of_ints()
    |> Enum.map(fn [int] -> Integer.digits(int) end)
    |> Enum.sum_by(&max_joltage(&1, n, 0))
  end

  def max_joltage(_digits, 0, max), do: max
  def max_joltage(digits, n, max) do
    x = digits |> Enum.reverse() |> Enum.drop(n - 1) |> Enum.reverse() |> Enum.max()
    {_, [_ | rest]} = Enum.split_while(digits, &(&1 < x))
    max_joltage(rest, n - 1, max + 10 ** (n - 1) * x)
  end
end
Gearhartlove

Gearhartlove

Thanks for sharing this solution, it helped me out a ton. I love the Enum.max_by(fn {a, i} -> {a, -i} end) because it’s a great way to select the “left most” maximum. I also didn’t realize that you perform max and min on tuples, great to learn that they compare their values left to right. Thanks again!

brownerd

brownerd

defmodule Day03 do
  @spec part_one(String.t()) :: non_neg_integer
  def part_one("priv/" <> _rest_of_path = path_to_data) do
    path_to_data
    |> stream_input()
    |> Enum.map(&take_n_max_batteries_from_bank_in_order(&1, 2))
    |> Enum.reduce(0, fn number_string, acc -> String.to_integer(number_string) + acc end)
  end

  @spec part_two(String.t()) :: non_neg_integer
  def part_two("priv/" <> _rest_of_path = path_to_data) do
    path_to_data
    |> stream_input()
    |> Enum.map(&take_n_max_batteries_from_bank_in_order(&1, 12))
    |> Enum.reduce(0, fn number_string, acc -> String.to_integer(number_string) + acc end)
  end

  def take_n_max_batteries_from_bank_in_order(line, take), do: do_find(line, take)

  defp do_find(line, take) do
    <<bank::binary-size(byte_size(line) - take + 1), buffer::binary>> = line
    do_find(bank, buffer, take)
  end

  @reset ""

  defp do_find(bank, buffer, take, rest \\ "", max \\ "", collected \\ "")
  defp do_find(_, _, take, _, _, collected) when byte_size(collected) == take, do: collected
  defp do_find(<<b>> <> bank, buffer, take, max, _rest, collected) when <<b>> > max, 
	  do: do_find(bank, buffer, take, <<b>>, @reset, collected)
  defp do_find(<<b>> <> bank, buffer, take, max, rest, collected), 
	  do: do_find(bank, buffer, take, max, rest <> <<b>>, collected)
  defp do_find("", "", take, max, seen, collected), 
	  do: do_find(seen, @reset, take, @reset, @reset, collected <> max)
  defp do_find("", <<b>> <> buffer, take, max, seen, collected), 
	  do: do_find(seen <> <<b>>, buffer, take, @reset, @reset, collected <> max)

  # ------------------------------------------------------------------------------

  defp stream_input(input) do
    input
    |> File.stream!()
    |> Stream.map(&String.trim/1)
  end
end
adamu

adamu

This is really nice. I also today TIL learned Integer.undigits/2, although replacing a string round-trip with that didn’t make my solution faster :sweat_smile:

lkuty

lkuty

Ooops forgot about Integer.undigits/2.

lkuty

lkuty

#!/usr/bin/env elixir

# Advent of Code 2025. Day 3

defmodule M do
  def largest_joltage_2(bank) do
    bank
    |> String.codepoints()
    |> Enum.map(&String.to_integer/1)
    |> Enum.reduce({nil, nil}, fn
      d, {nil, nil} -> {d, nil}
      d, {d1, nil} -> {d1, d}
      d, {d1, d2} when d1*10+d > d1*10+d2 and d1*10+d > d2*10+d -> {d1, d}
      d, {d1, d2} when d2*10+d > d1*10+d2 -> {d2, d}
      _d, {d1, d2} -> {d1, d2}
    end)
    |> then(fn {d1, d2} -> d1*10+d2 end)
  end

  def largest_joltage_12(bank) do
    bank
    |> String.codepoints()
    |> Enum.map(&String.to_integer/1)
    |> Enum.reduce([], fn
      d, lst when length(lst) < 12 -> [d | lst]
      d, lst -> max_list(d, lst)
    end)
    |> lst_to_num()
  end

  defp max_list(d, lst) do
    Enum.max_by([
      lst,
      [d | List.delete_at(lst, 0)],
      [d | List.delete_at(lst, 1)],
      [d | List.delete_at(lst, 2)],
      [d | List.delete_at(lst, 3)],
      [d | List.delete_at(lst, 4)],
      [d | List.delete_at(lst, 5)],
      [d | List.delete_at(lst, 6)],
      [d | List.delete_at(lst, 7)],
      [d | List.delete_at(lst, 8)],
      [d | List.delete_at(lst, 9)],
      [d | List.delete_at(lst, 10)],
      [d | List.delete_at(lst, 11)],
    ], &lst_to_num/1)
  end
  defp lst_to_num(lst), do: lst |> Enum.reverse() |> Enum.reduce(0, fn d, total -> total*10+d end)
end

# Part 1
File.read!("../day03.txt")
|> String.split()
|> Enum.map(&M.largest_joltage_2/1)
|> Enum.sum()
|> IO.inspect(label: "Day 3. Part 1")

# Part 2
File.read!("../day03.txt")
|> String.split()
|> Enum.map(&M.largest_joltage_12/1)
|> Enum.sum()
|> IO.inspect(label: "Day 3. Part 2")
Aetherus

Aetherus

Yes, it is memoization. I don’t know what your definition of tree search is though, cuz there’s no tree in my implementation.

My memo stores the tuple {lights_need_to_turn_on, index} as the keys, and max_jolt_in_the_subarray_from_index_and_after as the values.

BartOtten

BartOtten

Here we go.

Who knows?
How to do a ‘selective capture’?

The reducer passes 2 arguments to the anonymous function. Thought I could simply do (&2) but that is not allowed. &1 has to be used. The dirty trick is to (&2 || &1) when you are certain &2 will never be falsy but it is stretching the limits.

Could have gone with a simple fn but am wondering if someone knows a nice solid trick.

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

  def parse(input, _part) do
    Input.read!(input)
    |> String.trim()
    |> String.split("\n")
    |> Enum.map(&String.to_charlist/1)
  end

  def part_one(problem) do
    solve(problem, 2)
  end

  def part_two(problem) do
    solve(problem, 12)
  end

  def solve(problem, limit) do
    problem
    |> Stream.map(&keep_highest(&1, limit))
    |> Stream.map(&to_string/1)
    |> Stream.map(&String.to_integer/1)
    |> Enum.sum()
  end

  def keep_highest(bank, limit) do
      discard = length(bank) - limit
      Enum.reduce(1..discard, bank, &maximize/2)
  end

  def maximize(_reduction, bank), do: maximize(bank)
  def maximize([x, s]), do: [max(x, s)]
  def maximize([l, r | rest]) when l < r, do: [r | rest]
  def maximize([l, r | rest]), do: [l | maximize([r | rest])]
end

Edit 1: You can play “spot the differences” with @hauleth solution :slight_smile:

dompdv

dompdv

My solution, after a first attempt doing a never ending search (I’ve forgotten about memoization: I should memorize memoization more often). I thought my nice optimizations would do it, but finally they didn’t.
I found a more direct way

defmodule AdventOfCode.Solution.Year2025.Day03 do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(fn bank ->
      bank |> to_charlist() |> Enum.map(&(&1 - ?0))
    end)
  end

  ### Part 1
  def largest_two(bank) do
    bank_wi = Enum.with_index(bank)
    Enum.max(for {n1, i1} <- bank_wi, {n2, i2} <- bank_wi, i1 < i2, do: n1 * 10 + n2)
  end

  def part1(input), do: parse(input) |> Enum.map(&largest_two/1) |> Enum.sum()

  ### Part 2
  # End of recursion: all switches are activated
  def search(_, cumul, -1), do: cumul

  def search(rest, cumul, n_switches) do
    # Consider the max number on the right (keeping enough numbers for all the switches that are still to activate)
    max_in_remaining = rest |> Enum.slice(0..(length(rest) - n_switches - 1)) |> Enum.max()
    # Drop the numbers to the right until (and including) this max
    {_, [_max_in_remaining | r]} = Enum.split_while(rest, &(&1 != max_in_remaining))
    # Loop recursively
    search(r, cumul + max_in_remaining * Integer.pow(10, n_switches), n_switches - 1)
  end

  def part2(input), do: parse(input) |> Enum.map(&search(&1, 0, 11)) |> Enum.sum()
end
dompdv

dompdv

I’d be happy to have a commented code. Your solution looks impressive but I don’t understand at all how it works! Is it a tree search with memoization ?

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
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
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews