Powered by AppSignal & Oban Pro

Advent of code 2025 day 5

aoc2025day5.livemd

Advent of code 2025 day 5

Mix.install([
  {:kino, "~> 0.18"}
])

Part 1

https://adventofcode.com/2025/day/5

input = Kino.Input.textarea("Please give me input:")
[ranges_parts, ids_parts] =
  Kino.Input.read(input)
  |> String.split("\n\n", trim: true)

unique_ids =
  String.split(ids_parts, "\n", trim: true) |> Enum.map(&String.to_integer(&1))

overlapping_ranges =
  String.split(ranges_parts, "\n", trim: true)
  |> Enum.map(fn range ->
    [from, to] = String.split(range, "-")
    String.to_integer(from)..String.to_integer(to)//1
  end)

length(overlapping_ranges)
defmodule Part1 do
  def remove_numbers(ids, range) do
    Enum.reject(ids, fn ingredient_id -> ingredient_id in range end)
  end

  def get_spoiled_ingredients(overlapping_ranges, unique_ids) do
    Enum.reduce_while(overlapping_ranges, unique_ids, fn range, ids_minus_fresh ->
      new_ids_minus_fresh = remove_numbers(ids_minus_fresh, range)

      if new_ids_minus_fresh == [] do
        {:halt, []}
      else
        {:cont, new_ids_minus_fresh}
      end
    end)
  end
end

spoiled_ids = Part1.get_spoiled_ingredients(overlapping_ranges, unique_ids)

length(unique_ids) - length(spoiled_ids)

Part 2

defmodule Part2 do

  # ranges_without_overlap contains the handled ranges
  def combine_ranges(from..to//1, low..high//1, ranges_without_overlap)
      when low > to or high < from do
    # no overlap
    [low..high//1 | [from..to//1 | ranges_without_overlap]]
  end

  def combine_ranges(from..to//1, low..high//1, ranges_without_overlap) do
    # There is a new combination to be made to eliminate the overlap.
    [min(from, low)..max(to, high)//1 | ranges_without_overlap]
  end

  def loop_all_ranges_and_combine([]), do: []

  def loop_all_ranges_and_combine([first_range | overlapping_ranges]) do
    Enum.reduce(overlapping_ranges, [first_range], fn new_range,
                                                      [last_addition | ranges_without_overlap] ->
      combine_ranges(last_addition, new_range, ranges_without_overlap)
    end)
  end
end

# by sorting the ranges first, there will be no need to start from the beginning of 
# the list with ranges again when 2 ranges are combined.
sorted_overlapping_ranges = Enum.sort_by(overlapping_ranges, fn from..to//1 -> {from, to} end)
ranges_without_overlap = Part2.loop_all_ranges_and_combine(sorted_overlapping_ranges)

Enum.sum_by(ranges_without_overlap, fn from..to//1 -> to - from + 1 end)