bjorng

bjorng

Erlang Core Team

Advent of Code 2025 - Day 5

Easy puzzles two days in a row. I suspect tomorrow’s puzzle will not be that easy…

defmodule Day05 do
  def part1(input) do
    {ranges, ingredients} = parse(input)
    Enum.count(ingredients, &in_any_range?(&1, ranges))
  end

  defp in_any_range?(ingredient, ranges) do
    Enum.any?(ranges, &(ingredient in &1))
  end

  def part2(input) do
    {ranges, _ingredients} = parse(input)
    ranges = ranges
    |> Enum.sort
    |> combine_ranges
    Enum.reduce(ranges, 0, &(Range.size(&1) + &2))
  end

  defp combine_ranges([r]), do: [r]
  defp combine_ranges([r1, r2 | rest]) do
    case Range.disjoint?(r1, r2) do
      true ->
        [r1 | combine_ranges([r2 | rest])]
      false ->
        r = min(r1.first, r2.first) .. max(r1.last, r2.last)
        combine_ranges([r | rest])
    end
  end

  defp parse([ranges, ingredients]) do
    ranges = Enum.map(ranges, fn range ->
      [first, last] = String.split(range, "-")
      String.to_integer(first) .. String.to_integer(last)
    end)
    ingredients = Enum.map(ingredients, &String.to_integer(&1))
    {ranges, ingredients}
  end
end

First 10 of 18 Posts Switch mode

mudasobwa

mudasobwa

Creator of Cure

For the first time I needed a helper private function with multiple heads to handle a recursion properly.

  defmodule Day5 do
    @input "day5_1.input" |> File.read!() |> String.split("\n\n", trim: true)

    def calc([ranges, ids] \\ @input) do
      ranges =
        ranges
        |> String.split(["\s", "\n"], trim: true)
        |> Enum.map(&String.split(&1, "-", trim: true))
        |> Enum.map(fn [b, e] -> String.to_integer(b)..String.to_integer(e)//1 end)

      ids
      |> String.split(["\s", "\n"], trim: true)
      |> Enum.reduce({0, []}, fn id, {count, ids} ->
        id = String.to_integer(id)

        Enum.reduce_while(ranges, {count, ids}, fn range, {count, ids} ->
          if id in range, do: {:halt, {count + 1, [id | ids]}}, else: {:cont, {count, ids}}
        end)
      end)
    end

    def in_ranges([ranges, _ids] \\ @input) do
      ranges
      |> String.split(["\s", "\n"], trim: true)
      |> Enum.map(&String.split(&1, "-", trim: true))
      |> Enum.map(fn range -> Enum.map(range, &String.to_integer/1) end)
      |> Enum.sort()
      |> merge([])
      |> Enum.sum_by(fn [f, l] -> l - f + 1 end)
    end

    defp merge([], acc), do: Enum.reverse(acc)

    defp merge([range | rest], []), do: merge(rest, [range])

    defp merge([[f2, l2] | rest], [[f1, l1] | acc]) do
      if f2 <= l1 + 1 do
        merged = [f1, max(l1, l2)]
        merge(rest, [merged | acc])
      else
        merge(rest, [[f2, l2], [f1, l1] | acc])
      end
    end
  end
vkryukov

vkryukov

Yep, today was an easy one; parsing takes almost more lines than the rest of the code :).

defmodule Y2025.Day05 do
  def fresh?(_id, []), do: false

  def fresh?(id, [{a, b} | rest]) do
    cond do
      id < a -> false
      a <= id && id <= b -> true
      true -> fresh?(id, rest)
    end
  end

  def parse(s) do
    [ranges, ids] = String.split(s, "\n\n")

    ranges =
      ranges
      |> String.split("\n")
      |> Enum.map(fn line ->
        [a, b] = String.split(line, "-")
        {String.to_integer(a), String.to_integer(b)}
      end)
      |> Enum.sort() # <- important for fresh? and do_part2 to work!

    ids = ids |> String.split("\n") |> Enum.map(&String.to_integer/1)

    {ranges, ids}
  end

  def part1(s) do
    {ranges, ids} = parse(s)

    ids
    |> Enum.filter(fn id -> fresh?(id, ranges) end)
    |> length()
  end

  def part2(s) do
    {ranges, _} = parse(s)

    do_part2(ranges, 0)
  end

  def do_part2([], n), do: n
  def do_part2([{a, b}], n), do: n + b - a + 1

  def do_part2([{a, b}, {c, d} | rest], n) do
    cond do
      b < c -> do_part2([{c, d} | rest], n + b - a + 1)
      b >= d -> do_part2([{a, b} | rest], n)
      # b >= c & b < d
      true -> do_part2([{a, d} | rest], n)
    end
  end
end

vkryukov

vkryukov

Nice - TIL that Range module exists :). cc: @bjorng

sevenseacat

sevenseacat

Author of Ash Framework

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

I don’t want to say I’m disappointed by the puzzles this year, but… so far I kind of am :frowning: I expected at least one curly one so far… maybe tomorrow!

hauleth

hauleth

That is why I created RangeSet:

Parse

[fresh, ingridients] = String.split(puzzle_input, "\n\n")

fresh =
  fresh
  |> String.split()
  |> Enum.map(fn range ->
    [a, b] = range |> String.split("-") |> Enum.map(&String.to_integer/1)

    a..b//1
  end)
  |> RangeSet.new()

ingridients =
  ingridients
  |> String.split()
  |> Enum.map(&String.to_integer/1)

Part 1

Enum.count(ingridients, & &1 in fresh)

Part 2

Enum.count(fresh)
lud

lud

Same solution as everyone I guess :smiley:

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

  def parse(input, _part) do
    [ranges, ids] = input |> Input.read!() |> String.split("\n\n")
    ranges = ranges |> lines() |> Enum.map(&parse_range/1)
    ids = ids |> lines() |> Enum.map(&String.to_integer/1)
    {ranges, ids}
  end

  defp lines(text), do: String.split(text, "\n", trim: true)

  defp parse_range(str) do
    [a, b] = String.split(str, "-")
    String.to_integer(a)..String.to_integer(b)
  end

  def part_one({ranges, ids}) do
    Enum.count(ids, fn id -> Enum.any?(ranges, &(id in &1)) end)
  end

  def part_two({ranges, _}) do
    ranges
    |> Enum.sort_by(fn a..b//_ -> {a, b} end)
    |> reduce_ranges()
    |> Enum.sum_by(&Range.size/1)
  end

  defp reduce_ranges([a..b//_, c..d//_ | rest]) when c > b do
    [a..b | reduce_ranges([c..d | rest])]
  endI

  defp reduce_ranges([a..b//s, c..d//_ | rest]) when c in a..b//s do
    reduce_ranges([a..max(b, d) | rest])
  end

  defp reduce_ranges([last]) do
    [last]
  end
end

Except @hauleth 's , that RangeSet could come in handy for some other puzzles as well :smiley: I have the same thing but for rectangles, there is always that puzzle each year :slight_smile:

BartOtten

BartOtten

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

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

# couln't help myself
  def get_fresh!(ranges) do
    ranges
    |> String.split("\n", trim: true)
    |> Enum.map(&String.split(&1, "-"))
    |> Enum.map(fn [x, y] -> {String.to_integer(x), String.to_integer(y)} end)
  end

  def get_available(ingredients) do
    ingredients
    |> String.split("\n", trim: true)
    |> Enum.map(fn x -> String.to_integer(x) end)
  end


  def part_one([ranges, ingredients]) do
    fresh = get_fresh!(ranges)
    available = get_available(ingredients)

    Enum.count(available, fn a ->
      Enum.any?(fresh, fn {start, stop} -> a >= start and a <= stop end)
    end)
  end

  def part_two([ranges, _ingredients]) do
    fresh = get_fresh!(ranges)

    Enum.sort(fresh, fn {a, _b}, {c, _d} -> a < c end)
    |> merge_ranges()
    |> sum_ranges()
  end

  def merge_ranges([{_a, b} = s1, {c, _d} = s2 | rest]) when c > b,
    do: [s1 | merge_ranges([s2 | rest])]

  def merge_ranges([{a, b}, {c, d} | rest]) when c in a..b//1,
    do: merge_ranges([{a, max(b, d)} | rest])

  def merge_ranges(last), do: last

  def sum_ranges(ranges) do
    Enum.reduce(ranges, 0, fn {start, stop}, sum -> sum + (stop - start) + 1 end)
  end
end

No Range was harmed during development

rvnash

rvnash

It was nice that I got to use Range, and pattern matching on Range’s. I’ve never done that before. I didn’t try it, but I suspect that if tried to do something like accumulate all the values into a MapSet or something, it would take forever and use up all your memory.

Edit: Duh, of course it would with over 300 trillion values.

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

  def parse(input, _part) do
    Input.read!(input)
    |> String.split("\n\n")
    |> then(fn [ranges_str, ingredients_str] ->
      ranges =
        String.trim(ranges_str)
        |> String.split("\n")
        |> Enum.map(fn range_str ->
          range_str
          |> String.split("-")
          |> then(fn [i1_str, i2_str] ->
            String.to_integer(i1_str)..String.to_integer(i2_str)
          end)
        end)

      ingredients =
        String.trim(ingredients_str)
        |> String.split("\n")
        |> Enum.map(&String.to_integer/1)

      {ranges, ingredients}
    end)
  end

  def part_one({ranges, ingredients}) do
    ingredients |> Enum.filter(&is_fresh?(&1, ranges)) |> Enum.count()
  end

  def part_two({ranges, _ingredients}) do
    count_fresh_ranges(ranges)
  end

  defp is_fresh?(ingredient, ranges) do
    Enum.any?(ranges, fn range -> ingredient in range end)
  end

  defp count_fresh_ranges(ranges) do
    union_ranges(ranges)
    |> Enum.sum_by(&Range.size/1)
  end

  defp union_ranges(ranges) do
    ranges
    |> Enum.sort(fn r1_s.._//1, r2_s.._//1 ->
      r1_s <= r2_s
    end)
    |> union_into([])
  end

  defp union_into([], acc), do: acc
  defp union_into([range | []], acc), do: [range | acc]

  defp union_into([range1 | [range2 | ranges]], acc) do
    if Range.disjoint?(range1, range2) do
      union_into([range2 | ranges], [range1 | acc])
    else
      s1..e1//1 = range1
      s2..e2//1 = range2
      union_into([min(s1, s2)..max(e1, e2) | ranges], acc)
    end
  end
end
rvnash

rvnash

RangeSet is awesome!

dompdv

dompdv

The key is to sort the ranges. Too bad I’ve not thought about doing so :wink:. The result is, of course, far too complex.


defmodule AdventOfCode.Solution.Year2025.Day05 do
  # Part 1
  def in_interval?(i, {l, h}), do: l <= i and i <= h

  def in_any_interval?(ingredient, intervals),
    do: Enum.any?(for interval <- intervals, do: in_interval?(ingredient, interval))

  def part1(input) do
    {intervals, ingredients} = parse(input)
    Enum.count(ingredients, &in_any_interval?(&1, intervals))
  end

  # Part 2
  def distinct?({a, b}, {c, d}), do: b < c or d < a
  def merge_overlapping({a, b}, {c, d}) when c < a, do: merge_overlapping({c, d}, {a, b})
  def merge_overlapping({a, b}, {_c, d}), do: {a, max(b, d)}

  def merge([], intervals_in_stock), do: intervals_in_stock

  def merge([to_add | to_merge], intervals_in_stock) do
    case Enum.split_while(intervals_in_stock, &distinct?(&1, to_add)) do
      {_, []} ->
        merge(to_merge, [to_add | intervals_in_stock])

      {distincts, [overlapping | in_stock]} ->
        merged_interval = merge_overlapping(to_add, overlapping)
        merge([merged_interval | to_merge], distincts ++ in_stock)
    end
  end

  def add_interval_sizes(intervals) do
    intervals |> Enum.map(fn {l, h} -> h - l + 1 end) |> Enum.sum()
  end

  def part2(input) do
    elem(parse(input), 0)
    |> merge([])
    |> add_interval_sizes()
  end

  def parse(input) do
    [intervals, ingredients] = String.split(input, "\n\n", trim: true)

    p_intervals =
      for int <- String.split(intervals, "\n") do
        [l, h] = String.split(int, "-")
        {String.to_integer(l), String.to_integer(h)}
      end

    p_ingredients =
      for ing <- String.split(ingredients, "\n", trim: true), do: String.to_integer(ing)

    {p_intervals, p_ingredients}
  end
end

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