bjorng

bjorng

Erlang Core Team

Advent of Code 2025 - Day 7

Part 1 took much more time than part 2. I started out by reusing my grid parsing function from day 4 and start coding before I had fully understood the splitting rules and how to count splits. It ended up in a mess.

After having finished part 2, I rewrote part 1 using the new parsing routine that I wrote for part 2.

defmodule Day07 do
  def part1(input) do
    {start, splitters} = parse_splitters(input)
    beams = [start]
    split_beams(beams, splitters, 0)
  end

  defp split_beams(_beams, [], num_splits), do: num_splits
  defp split_beams(beams, [splits | splitters], num_splits) do
    {beams, num_splits} = split_beams(beams, splits, [], num_splits)
    split_beams(beams, splitters, num_splits)
  end

  defp split_beams([], _splitters, new, num_splits) do
    {Enum.uniq(new), num_splits}
  end
  defp split_beams([column | beams], splits, new, num_splits) do
    case Enum.member?(splits, column) do
      true ->
        new = [column - 1, column + 1 | new]
        split_beams(beams, splits, new, num_splits + 1)
      false ->
        new = [column | new]
        split_beams(beams, splits, new, num_splits)
    end
  end

  def part2(input) do
    {start, splitters} = parse_splitters(input)
    {timelines, _} = count_timelines(start, splitters, %{})
    timelines
  end

  defp count_timelines(column, splitters, worlds) do
    key = {column, length(splitters)}
    case worlds do
      %{^key => timelines} ->
        {timelines, worlds}
      %{} ->
        {timelines, worlds} = count_timelines_split(column, splitters, worlds)
        {timelines, Map.put(worlds, key, timelines)}
    end
  end

  defp count_timelines_split(_column, [], worlds) do
    {1, worlds}
  end
  defp count_timelines_split(column, [splits | splitters], worlds) do
    case Enum.member?(splits, column) do
      true ->
        {timelines1, worlds} = count_timelines(column - 1, splitters, worlds)
        {timelines2, worlds} = count_timelines(column + 1, splitters, worlds)
        timelines = timelines1 + timelines2
        {timelines, worlds}
      false ->
        count_timelines(column, splitters, worlds)
    end
  end

  defp parse_splitters(input) do
    splitters = input
    |> Enum.map(fn line ->
      String.to_charlist(line)
      |> Enum.with_index
      |> Enum.flat_map(fn {char, col} ->
        case char do
          ?. -> []
          ?S -> [{:start, col}]
          ?^ -> [col]
        end
      end)
    end)
    [[{:start, start}] | splitters] = splitters
    {start, splitters}
  end
end

EDIT: Further simplified part 1 by removing vestiges of my messy first solution.

Most Liked

hauleth

hauleth

Parse

[start | rest] = String.split(puzzle_input)

start_col = byte_size(start) - byte_size(String.trim_leading(start, "."))

splitters =
  Enum.map(rest, fn row ->
    row
    |> String.to_charlist()
    |> Enum.with_index()
    |> Enum.filter(&(elem(&1, 0) == ?^))
    |> MapSet.new(&elem(&1, 1))
  end)

Part 1

Enum.reduce(splitters, {MapSet.new([start_col]), 0}, fn splits, {beams, count} ->
  import MapSet, only: [intersection: 2, difference: 2, union: 2]
  
  hits = intersection(beams, splits)
  new_beams = for hit <- hits, dx <- [-1, 1], into: MapSet.new(), do: hit + dx
  beams = beams |> difference(hits) |> union(new_beams)

  {beams, MapSet.size(hits) + count}
end)

Part 2

Enum.reduce(splitters, %{start_col => 1}, fn splits, beams ->
  Enum.reduce(splits, beams, fn s, acc ->
    case Map.pop(acc, s) do
      {nil, map} ->
        map

      {count, map} ->
        Map.merge(
          map,
          %{
            (s + 1) => count,
            (s - 1) => count
          },
          fn _k, a, b -> a + b end
        )
    end
  end)
end)
|> Enum.sum_by(&elem(&1, 1))

EDIT

I decided to draw the resulting image

rvnash

rvnash

I think it’s called “Pascal’s Triangle”, dunno maybe I’m wrong. Anyway, I naively did Part 2 and of course it doesn’t complete in the age of the universe. So, decided to keep track of both timelines and tachyons in one pass. Went much better. Used MapSet and Map to speed lookups. Fun day!

Edit: It is Pascal’s Triangle except with the twist that the tree doesn’t split necessarily evenly on each level.

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

  def parse(input, _part) do
    Input.read!(input)
    |> String.split("\n", trim: true)
    |> then(fn [first_line | rest] ->
      {first_line
       |> String.graphemes()
       |> Enum.with_index()
       |> Enum.filter(fn {c, _n} -> c == "S" end)
       |> List.first()
       |> elem(1),
       rest
       |> Enum.map(fn line ->
         line
         |> String.graphemes()
         |> Enum.with_index()
         |> Enum.reject(&(elem(&1, 0) == "."))
         |> Enum.map(&elem(&1, 1))
         |> MapSet.new()
       end)}
    end)
  end

  def part_one(problem) do
    tach_counters(problem)
    |> elem(1)
  end

  def part_two(problem) do
    tach_counters(problem)
    |> elem(0)
    |> Enum.sum_by(fn {_, n} -> n end)
  end

  defp tach_counters({tachyon, rows}) do
    tachyons_w_timelines = %{tachyon => 1}

    Enum.reduce(rows, {tachyons_w_timelines, 0}, fn row, {tachyons_w_timelines, split_count} ->
      Enum.reduce(tachyons_w_timelines, {tachyons_w_timelines, split_count}, fn {n, last},
                                                                                {tachyons_w_timelines,
                                                                                 split_count} ->
        if MapSet.member?(row, n) do
          {tachyons_w_timelines
           |> Map.delete(n)
           |> Map.update(n + 1, last, &(&1 + last))
           |> Map.update(n - 1, last, &(&1 + last)), split_count + 1}
        else
          {tachyons_w_timelines, split_count}
        end
      end)
    end)
  end
end
dompdv

dompdv

Simplified after doing Part2 because Part2 solves also Part1.

defmodule AdventOfCode.Solution.Year2025.Day07 do
  def part1(input), do: input |> fire_beam() |> take_counter()
  def part2(input), do: input |> fire_beam() |> take_timelines() |> Map.values() |> Enum.sum()
  def take_counter(a), do: elem(a, 1)
  def take_timelines(a), do: elem(a, 0)

  def fire_beam(input) do
    [[start] | rows] = parse(input)
    beam(Map.new([{start, 1}]), 0, rows)
  end

  def beam(in_flight, hits_counter, []), do: {in_flight, hits_counter}

  def beam(in_flight, hits_counter, [next_row_splitters | rows]) do
    # in_flight %{column => number of timelines leading to this point}
    # hits_counter : number of hits on a splitter
    {new_in_flight, new_hits} =
      Enum.reduce(in_flight, {%{}, 0}, fn {col, n_timelines}, {next_positions, hits} ->
        if col in next_row_splitters,
          do: {
            next_positions
            |> Map.update(col - 1, n_timelines, &(&1 + n_timelines))
            |> Map.update(col + 1, n_timelines, &(&1 + n_timelines)),
            hits + 1
          },
          else: {Map.update(next_positions, col, n_timelines, &(&1 + n_timelines)), hits}
      end)

    beam(new_in_flight, hits_counter + new_hits, rows)
  end

  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.reduce([], fn line, lines ->
      splitter_cols =
        to_charlist(line)
        |> Enum.with_index()
        |> Enum.reduce([], fn
          {?., _}, acc -> acc
          {?^, col}, l -> [col | l]
          {?S, col}, l -> [col | l]
        end)

      if splitter_cols == [], do: lines, else: [splitter_cols | lines]
    end)
    |> Enum.reverse()
  end
end

Where Next?

Popular in Challenges Top

bismark
Took me a minute to remember my binary math :smile: :grimacing:.. import Bitwise __DIR__ |&gt; Path.join("puzzle.txt") |&gt; File.strea...
New
bjorng
This topic is about Day 15 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New
bjorng
This topic is about Day 9 of the Advent of Code 2021 . We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New
kwando
Took a while, but another use case for “move vectors” today and pattern matching. :slight_smile: The trick was to first generate a list...
New
antoine-duchenet
Everything went smoothly today. Nothing to change to solve part 2 because I already used memoization for part 1 (it looked like an AoC e...
New
sukhmeetsd
All in all, from what I understand, it is better not to use GenServer.cast when we want some concurrent operations to happen for sure, be...
New
bjorng
Here is my solution for day 4: https://github.com/bjorng/advent-of-code/blob/main/2024/day04/lib/day04.ex
New
Aetherus
I tried to use combinatorial to solve today’s puzzles but failed (my brain burned out :exploding_head:). In the end I just used brute for...
New
New
Aetherus
Don’t know why the regex ~r/[\W &amp;&amp; [^\.]]/x does not work in Elixir. It works pretty well in Ruby. Anyway, here is my solution: ...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
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
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
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36128 110
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
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

We're in Beta

About us Mission Statement