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

Most Liked

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

Last Post!

billylanchantin

billylanchantin

For part 2, I flatten a list of range firsts and lasts and keep a running total of how many ranges overlap our place in the list.

defmodule Day05 do
  def part1(file) do
    {ranges, ids} = parse(file)
    Enum.count(ids, fn id -> Enum.any?(ranges, &(id in &1)) end)
  end

  def part2(file) do
    {ranges, _} = parse(file)
    firsts = Enum.frequencies_by(ranges, & &1.first)
    lasts = Enum.frequencies_by(ranges, &(&1.last + 1))

    Enum.sort(Map.keys(firsts) ++ Map.keys(lasts))
    |> Enum.scan({0, 0}, fn x, {_, c} -> {x, c + (firsts[x] || 0) - (lasts[x] || 0)} end)
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.sum_by(fn [{a, count}, {b, _}] -> if count > 0, do: b - a, else: 0 end)
  end

  def parse(file) do
    [rngs, ids] = file |> File.read!() |> split("\n\n") |> Enum.map(&split(&1, "\n"))
    {Enum.map(rngs, &to_rng/1), Enum.map(ids, &to_int/1)}
  end

  def split(str, on), do: String.split(str, on, trim: true)
  def to_int(str), do: str |> Integer.parse() |> elem(0)
  def to_rng(str), do: str |> split("-") |> Enum.map(&to_int/1) |> then(&apply(Range, :new, &1))
end

Where Next?

Popular in Challenges Top

bjorng
Note: This topic is to talk about Day 18 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
Aetherus
This topic is about Day 5 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
LostKobrakai
This one has been quite the ride. Struggled at first to find a good data format to suite the problem. I really like how that turned out b...
New
adamu
Nobody’s doing Advent of Code this year? :grinning_face_with_smiling_eyes: I might do the first week or so. For Day 1, first I solved i...
New
Aetherus
The second part of today’s puzzle is very misleading. FYI, each of the ghosts has only one possible position that ends with a "Z" on its...
New
bjorng
My solution finishes both parts in 5 seconds on my computer. That time should be possible to reduce by optimizing my rather naive tilt/2 ...
New
bjorng
Here is my solution for day 2 of Advent of Code: https://github.com/bjorng/advent-of-code/blob/main/2024/day02/lib/day02.ex
New

Other popular topics Top

vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 40165 209
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement