Day 7 - Advent of Code 2025
Mix.install([:kino, :benchee])
Links
Prompt
— Day 7: Laboratories —
You thank the cephalopods for the help and exit the trash compactor, finding yourself in the familiar halls of a North Pole research wing.
Based on the large sign that says “teleporter hub”, they seem to be researching teleportation; you can’t help but try it for yourself and step onto the large yellow teleporter pad.
Suddenly, you find yourself in an unfamiliar room! The room has no doors; the only way out is the teleporter. Unfortunately, the teleporter seems to be leaking magic smoke.
Since this is a teleporter lab, there are lots of spare parts, manuals, and diagnostic equipment lying around. After connecting one of the diagnostic tools, it helpfully displays error code 0H-N0, which apparently means that there’s an issue with one of the tachyon manifolds.
You quickly locate a diagram of the tachyon manifold (your puzzle input). A tachyon beam enters the manifold at the location marked S; tachyon beams always move downward. Tachyon beams pass freely through empty space (.). However, if a tachyon beam encounters a splitter (^), the beam is stopped; instead, a new tachyon beam continues from the immediate left and from the immediate right of the splitter.
For example:
.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
In this example, the incoming tachyon beam (|) extends downward from S until it reaches the first splitter:
.......S.......
.......|.......
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
At that point, the original beam stops, and two new beams are emitted from the splitter:
.......S.......
.......|.......
......|^|......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
Those beams continue downward until they reach more splitters:
.......S.......
.......|.......
......|^|......
......|.|......
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
At this point, the two splitters create a total of only three tachyon beams, since they are both dumping tachyons into the same place between them:
.......S.......
.......|.......
......|^|......
......|.|......
.....|^|^|.....
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............
This process continues until all of the tachyon beams reach a splitter or exit the manifold:
.......S.......
.......|.......
......|^|......
......|.|......
.....|^|^|.....
.....|.|.|.....
....|^|^|^|....
....|.|.|.|....
...|^|^|||^|...
...|.|.|||.|...
..|^|^|||^|^|..
..|.|.|||.|.|..
.|^|||^||.||^|.
.|.|||.||.||.|.
|^|^|^|^|^|||^|
|.|.|.|.|.|||.|
To repair the teleporter, you first need to understand the beam-splitting properties of the tachyon manifold. In this example, a tachyon beam is split a total of 21 times.
Analyze your manifold diagram. How many times will the beam be split?
To begin, get your puzzle input.
— Part Two —
With your analysis of the manifold complete, you begin fixing the teleporter. However, as you open the side of the teleporter to replace the broken manifold, you are surprised to discover that it isn’t a classical tachyon manifold - it’s a quantum tachyon manifold.
With a quantum tachyon manifold, only a single tachyon particle is sent through the manifold. A tachyon particle takes both the left and right path of each splitter encountered.
Since this is impossible, the manual recommends the many-worlds interpretation of quantum tachyon splitting: each time a particle reaches a splitter, it’s actually time itself which splits. In one timeline, the particle went left, and in the other timeline, the particle went right.
To fix the manifold, what you really need to know is the number of timelines active after a single particle completes all of its possible journeys through the manifold.
In the above example, there are many timelines. For instance, there’s the timeline where the particle always went left:
.......S.......
.......|.......
......|^.......
......|........
.....|^.^......
.....|.........
....|^.^.^.....
....|..........
...|^.^...^....
...|...........
..|^.^...^.^...
..|............
.|^...^.....^..
.|.............
|^.^.^.^.^...^.
|..............
Or, there’s the timeline where the particle alternated going left and right at each splitter:
.......S.......
.......|.......
......|^.......
......|........
......^|^......
.......|.......
.....^|^.^.....
......|........
....^.^|..^....
.......|.......
...^.^.|.^.^...
.......|.......
..^...^|....^..
.......|.......
.^.^.^|^.^...^.
......|........
Or, there’s the timeline where the particle ends up at the same point as the alternating timeline, but takes a totally different path to get there:
.......S.......
.......|.......
......|^.......
......|........
.....|^.^......
.....|.........
....|^.^.^.....
....|..........
....^|^...^....
.....|.........
...^.^|..^.^...
......|........
..^..|^.....^..
.....|.........
.^.^.^|^.^...^.
......|........
In this example, in total, the particle ends up on 40 different timelines.
Apply the many-worlds interpretation of quantum tachyon splitting to your manifold diagram. In total, how many different timelines would a single tachyon particle end up on?
Although it hasn’t changed, you can still get your puzzle input.
Input
input = Kino.Input.textarea("Please paste your input file:")
input = input |> Kino.Input.read()
".......S.......\n...............\n.......^.......\n...............\n......^.^......\n...............\n.....^.^.^.....\n...............\n....^.^...^....\n...............\n...^.^...^.^...\n...............\n..^...^.....^..\n...............\n.^.^.^.^.^...^.\n..............."
Solution
defmodule Grid do
defstruct grid: %{}, start: nil, max_x: nil, max_y: nil
end
defmodule Day07 do
defdelegate parse(input), to: __MODULE__.Input
def part1(input) do
grid = parse(input)
{x, y} = grid.start
count_splits_p1(0, x + 1, [y], grid)
end
def part2(input) do
grid = parse(input)
{x, y} = grid.start
count_splits_p2(x + 1, %{y => 1}, grid)
end
defp count_splits_p1(split_count, x, _, grid) when x > grid.max_x,
do: split_count
defp count_splits_p1(split_count, x, ys, grid) do
{splits, not_split} = Enum.split_with(ys, &(grid.grid[{x, &1}] == ?^))
new_ys = Enum.flat_map(splits, fn y -> [y - 1, y + 1] end)
new_ys = Enum.uniq(new_ys ++ not_split)
new_count = split_count + Enum.count(splits)
count_splits_p1(new_count, x + 1, new_ys, grid)
end
defp count_splits_p2(x, ys, grid) when x > grid.max_x,
do: Enum.sum_by(ys, fn {_, count} -> count end)
defp count_splits_p2(x, ys, grid) do
{splits, not_split} =
Enum.split_with(ys, fn {y, _count} ->
grid.grid[{x, y}] == ?^
end)
splits = Enum.flat_map(splits, fn {y, count} -> [{y - 1, count}, {y + 1, count}] end)
new_ys =
(splits ++ not_split)
|> Enum.reduce(%{}, fn {key, value}, acc ->
Map.update(acc, key, value, fn existing -> existing + value end)
end)
count_splits_p2(x + 1, new_ys, grid)
end
defmodule Input do
def parse(input) when is_binary(input) do
input
|> String.splitter("\n", trim: true)
|> parse()
end
def parse(input) do
Stream.map(input, &String.to_charlist/1)
|> Enum.with_index()
|> Enum.reduce(%Grid{}, fn {line, x}, acc ->
line
|> Enum.with_index()
|> Enum.reduce(acc, fn
{?S, y}, acc ->
Map.put(acc, :start, {x, y})
{char, y}, acc ->
acc
|> Map.update!(:grid, &Map.put(&1, {x, y}, char))
|> Map.put(:max_y, y)
end)
|> Map.put(:max_x, x)
end)
end
end
end
{:module, Day07, <<70, 79, 82, 49, 0, 0, 21, ...>>, ...}
Analyze your manifold diagram. How many times will the beam be split?
Your puzzle answer was 1550.
Day07.part1(input)
21
Apply the many-worlds interpretation of quantum tachyon splitting to your manifold diagram. In total, how many different timelines would a single tachyon particle end up on?
Your puzzle answer was 9897897326778.
Day07.part2(input)
40
Both parts of this puzzle are complete! They provide two gold stars: **
At this point, you should return to your Advent calendar and try another puzzle.
If you still want to see it, you can get your puzzle input.
Tests
ExUnit.start(auto_run: false)
defmodule Day07Test do
use ExUnit.Case, async: false
setup_all do
[
input: ".......S.......\n...............\n.......^.......\n...............\n......^.^......\n...............\n.....^.^.^.....\n...............\n....^.^...^....\n...............\n...^.^...^.^...\n...............\n..^...^.....^..\n...............\n.^.^.^.^.^...^.\n..............."
]
end
describe "part1/1" do
test "returns expected value", %{input: input} do
assert Day07.part1(input) == 21
end
end
describe "part2/1" do
test "returns expected value", %{input: input} do
assert Day07.part2(input) == 40
end
end
end
ExUnit.run()
Running ExUnit with seed: 678749, max_cases: 16
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures
%{total: 2, excluded: 0, failures: 0, skipped: 0}
Benchmarking
defmodule Benchmarking do
# https://github.com/bencheeorg/benchee
def run(input) do
Benchee.run(
%{
"Part 1" => fn -> Day07.part1(input) end,
"Part 2" => fn -> Day07.part2(input) end
},
memory_time: 2,
reduction_time: 2
)
nil
end
end
{:module, Benchmarking, <<70, 79, 82, 49, 0, 0, 8, ...>>, ...}
Benchmarking.run(input)
Operating System: macOS
CPU Information: Apple M1
Number of Available Cores: 8
Available memory: 8 GB
Elixir 1.19.4
Erlang 28.2
JIT enabled: true
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 2 s
reduction time: 2 s
parallel: 1
inputs: none specified
Estimated total run time: 22 s
Excluding outliers: false
Benchmarking Part 1 ...
Benchmarking Part 2 ...
Calculating statistics...
Formatting results...
Name ips average deviation median 99th %
Part 1 158.43 6.31 ms ±8.96% 6.15 ms 7.43 ms
Part 2 153.60 6.51 ms ±7.93% 6.62 ms 7.14 ms
Comparison:
Part 1 158.43
Part 2 153.60 - 1.03x slower +0.198 ms
Memory usage statistics:
Name average deviation median 99th %
Part 1 15.36 MB ±0.00% 15.36 MB 15.36 MB
Part 2 16.07 MB ±0.00% 16.07 MB 16.07 MB
Comparison:
Part 1 15.36 MB
Part 2 16.07 MB - 1.05x memory usage +0.71 MB
Reduction count statistics:
Name Reduction count
Part 1 323.17 K
Part 2 368.38 K - 1.14x reduction count +45.21 K
**All measurements for reduction count were the same**
nil