vkryukov

vkryukov

Advent of Code 2025 - Day 8

Nice little problem. The right data structure is half the solution!

defmodule Y2025.Day08 do
  def pairs(enum) do
    l = enum |> Enum.with_index()
    for {a, i} <- l, {b, j} <- l, i < j, do: {a, b}
  end

  def norm([x1, y1, z1], [x2, y2, z2]),
    do: (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)

  defmodule Circuits do
    def new(enum), do: Enum.into(enum, %{}, fn x -> {x, MapSet.new([x])} end)

    def fuze(circuits, c1, c2) do
      s1 = circuits[c1]
      s2 = circuits[c2]

      if s1 == s2 do
        circuits
      else
        s = MapSet.union(s1, s2)
        s |> Enum.reduce(circuits, fn el, circuits -> Map.put(circuits, el, s) end)
      end
    end

    def product_top_3_sizes(circuits) do
      circuits
      |> Enum.uniq_by(&elem(&1, 1))
      |> Enum.map(fn {_, v} -> MapSet.size(v) end)
      |> Enum.sort(:desc)
      |> Enum.take(3)
      |> Enum.product()
    end
  end

  def parse(s), do: s |> String.split("\n") |> Enum.map(&parse_line/1) |> Circuits.new()

  def parse_line(s), do: s |> String.trim() |> String.split(",") |> Enum.map(&String.to_integer/1)

  def fuze_while(circuits, start, fun) do
    Map.keys(circuits)
    |> pairs()
    |> Enum.map(fn {a, b} -> {a, b, norm(a, b)} end)
    |> Enum.sort_by(&elem(&1, 2))
    |> Enum.reduce_while(start, fun)
  end

  def part1(s, n) do
    circuits = parse(s)

    fuze_while(circuits, {circuits, 0}, fn {c1, c2, _}, {circuits, count} ->
      if count == n do
        {:halt, circuits |> Circuits.product_top_3_sizes()}
      else
        {:cont, {Circuits.fuze(circuits, c1, c2), count + 1}}
      end
    end)
  end

  def part2(s) do
    circuits = parse(s)

    n = map_size(circuits)

    fuze_while(circuits, circuits, fn {c1, c2, _}, circuits ->
      if MapSet.union(circuits[c1], circuits[c2]) |> MapSet.size() == n do
        {:halt, List.first(c1) * List.first(c2)}
      else
        {:cont, Circuits.fuze(circuits, c1, c2)}
      end
    end)
  end
end

Most Liked

Aetherus

Aetherus

My code is almost the same as yours, except one difference: I added an ID (generated with make_ref()) to each MapSet.t() so that I don’t have to compare two whole sets, instead I just compare their ID.

defmodule AoC2025.Day08 do
  def part1(points) do
    distances = calc_distances(points, [])

    initial_acc = Map.new(points, fn point ->
      {
        point,  # key = {x, y, z}
        {make_ref(), MapSet.new([point])}  # value = {set_id, set}
      }
    end)

    {distances, initial_acc}
    |> Stream.iterate(fn {[{_, p1, p2} | distances], acc} ->
      {id1, set1} = acc[p1]
      {id2, set2} = acc[p2]
      
      if id1 == id2 do
        {distances, acc}
      else
        {_, merged_set} = union = {id1, MapSet.union(set1, set2)}
        {distances, Enum.reduce(merged_set, acc, fn p, acc ->
          Map.put(acc, p, union)
        end)}
      end
    end)
    |> Enum.at(1000)
    |> elem(1)
    |> Map.values()
    |> Enum.uniq_by(&elem(&1, 0))
    |> Enum.map(&elem(&1, 1))
    |> Enum.map(&MapSet.size/1)
    |> Enum.sort(:desc)
    |> Enum.take(3)
    |> Enum.product()
  end

  def part2(points) do
    distances = calc_distances(points, [])

    initial_acc = Map.new(points, fn point ->
      {
        point,  # key = {x, y, z}
        {make_ref(), MapSet.new([point])}  # value = {set_id, set}
      }
    end)

    {distances, initial_acc, nil, nil}
    |> Stream.iterate(fn {[{_, p1, p2} | distances], acc, x1, x2} ->
      {id1, set1} = acc[p1]
      {id2, set2} = acc[p2]
      
      if id1 == id2 do
        {distances, acc, x1, x2}
      else
        {_, merged_set} = union = {id1, MapSet.union(set1, set2)}
        {distances, Enum.reduce(merged_set, acc, fn p, acc ->
          Map.put(acc, p, union)
          end), elem(p1, 0), elem(p2, 0)}
      end
    end)
    |> Enum.find(fn {_, acc, _, _} ->
      acc
      |> Map.values()
      |> Enum.map(&elem(&1, 0))
      |> Enum.uniq()
      |> length()
      |> Kernel.==(1)
    end)
    |> then(fn {_, _, x1, x2} ->
      x1 * x2
    end)
  end


  defp calc_distances([], acc) do
    Enum.sort(acc)
  end

  defp calc_distances([{x1, y1, z1} = p1 | t], acc) do
    acc =
      for {x2, y2, z2} = p2 <- t, reduce: acc do
        acc ->
          distance = (x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2
          [{distance, p1, p2} | acc]
      end

    calc_distances(t, acc)
  end
end
hauleth

hauleth

AFAIK - no, but I think that in most cases it will be negligible. I would need to check how exactly map equality is implemented.

dompdv

dompdv

My solution, very late (I couldn’t find time today to work on it before 22:30);
Nothing new/fancy when I look at those already posted: pair distances are pre-computed and sorted, the main data structure is a map %{index of the circuit => MapSet of the box coordinates in the circuit}


defmodule AdventOfCode.Solution.Year2025.Day08 do
  # Launch processing for part1 & 2
  def part1(input), do: input |> do_connect(1000) |> mul_3_largest()
  def part2(input), do: input |> do_connect(:full_connect) |> mul_xs()

  # Post processing part 1
  def mul_3_largest(circuits) do
    circuits
    |> Enum.map(fn {_, c} -> MapSet.size(c) end)
    |> Enum.sort(:desc)
    |> Enum.take(3)
    |> Enum.reduce(1, &(&1 * &2))
  end

  # Post processing part 2
  def mul_xs({[x1, _, _], [x2, _, _]}), do: x1 * x2

  # The common algorithm : preparing the wiring process
  def do_connect(input, stop_at) do
    boxes_i = parse(input)

    sorted_connects =
      for({b1, i1} <- boxes_i, {b2, i2} <- boxes_i, i1 < i2, do: add_distance(b1, b2))
      |> Enum.sort_by(&elem(&1, 2))

    initial_circuits = for {b, i} <- boxes_i, into: %{}, do: {i, MapSet.new([b])}

    wire(sorted_connects, initial_circuits, 0, stop_at, nil)
  end

  def add_distance(b1, b2),
    do: {b1, b2, Enum.zip(b1, b2) |> Enum.map(fn {a, b} -> (a - b) * (a - b) end) |> Enum.sum()}

  # Wiring process itself
  # Stop if there is one circuit left
  def wire(_, circuits, _n, _stop_at, last) when map_size(circuits) == 1, do: last

  # Stop if we reach "stop_at" if stop_at is a number
  def wire(_, circuits, n, stop_at, _last) when stop_at != :full_connect and n == stop_at,
    do: circuits

  def wire([{b1, b2, _} | rest], circuits, n, stop_at, _last) do
    c_b1 = which_circuit(circuits, b1)
    c_b2 = which_circuit(circuits, b2)

    if c_b1 == c_b2 do
      # They are in the same circuit => do nothing
      wire(rest, circuits, n + 1, stop_at, {b1, b2})
    else
      # Different circuit, merge circuit c_b2 into c_b1
      new_circuits =
        circuits |> Map.delete(c_b2) |> Map.update(c_b1, nil, &MapSet.union(&1, circuits[c_b2]))

      wire(rest, new_circuits, n + 1, stop_at, {b1, b2})
    end
  end

  def which_circuit(circuits, b) do
    # returns the index of the circuit containing the box b
    Enum.reduce_while(circuits, nil, fn {i, c}, _acc ->
      if b in c, do: {:halt, i}, else: {:cont, nil}
    end)
  end

  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      line |> String.split(",", trim: true) |> Enum.map(&String.to_integer/1)
    end)
    |> Enum.with_index()
  end
end

Where Next?

Popular in Challenges Top

adamu
I said I was on a break, but I took a sneak peak and it looked fun so… Part 1 completes in half a millisecond with a single pass of the ...
New
igorb
I found today a bit tedious: advent-of-code-2024/lib/advent_of_code2024/day15.ex at main · ibarakaiev/advent-of-code-2024 · GitHub.
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
christhekeele
Continuation of Advent of Code 2022​:christmas_tree:, Day 1: Day 2! Leaderboard:
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
stevensonmt
Anyone else think the prompt for this challenge is contradictory? The rules for comparing packets include If both values are lists, c...
New
bjorng
Note: This topic is to talk about Day 9 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
cblavier
Hi, there :wave: Today, I felt it was way more challenging! I went through part2 thanks to Agent based memoization (without memoization ...
New
lud
Hello everyone! This year is going to be shorter, but the difficulty will grow faster. Today I already feel that this is not standard “D...
New
maennchen
Ok, that was a rough one today. I haven’t found a way to improve the algorithm further. Part 1 runs in .5 seconds, Part 2 in ~ 5 minutes...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127536 1222
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New

We're in Beta

About us Mission Statement