bjorng
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
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
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
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
Popular in Challenges
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










