bjorng

bjorng

Erlang Core Team

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

Showing Posts 1 to 10

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? Top

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
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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews