Skip to content

Commit d2485ba

Browse files
committed
2025: day 7
1 parent 0c5b1b4 commit d2485ba

File tree

6 files changed

+347
-10
lines changed

6 files changed

+347
-10
lines changed

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# [Advent of Code](https://adventofcode.com) in Rust 🦀
22

3-
![Stars: 512](https://img.shields.io/badge/Stars-512⭐-blue)
4-
![Rust: 256](https://img.shields.io/badge/Rust-256-cyan?logo=Rust)
3+
![Stars: 514](https://img.shields.io/badge/Stars-514⭐-blue)
4+
![Rust: 257](https://img.shields.io/badge/Rust-257-cyan?logo=Rust)
55
![Python: 127](https://img.shields.io/badge/Python-127-cyan?logo=Python)
66

77
<img src="./scripts/assets/christmas_ferris_2015_2024.png" alt="Christmas Ferris" width="164" />
@@ -10,7 +10,7 @@
1010

1111
Made for fun 😎 and to practice Rust. Many thanks to [Eric Wastl](https://twitter.com/ericwastl).
1212

13-
## 2025 (current event) ([Calendar](https://adventofcode.com/2025)) ([Solutions](src/year2025/)) : 12
13+
## 2025 (current event) ([Calendar](https://adventofcode.com/2025)) ([Solutions](src/year2025/)) : 14
1414

1515
Puzzle | Stars | Languages
1616
----------------------------------------------------------------- | ----- | -----------
@@ -19,13 +19,14 @@ Puzzle | Stars | Lang
1919
[Day 3: Lobby](https://adventofcode.com/2025/day/3) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day3/day3.rs) [![Python](./scripts/assets/python.png)](src/year2025/day3/day3.py) [![Go](./scripts/assets/go.png)](src/year2025/day3/day3.go)
2020
[Day 4: Printing Department](https://adventofcode.com/2025/day/4) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day4/day4.rs) [![Python](./scripts/assets/python.png)](src/year2025/day4/day4.py) [![Go](./scripts/assets/go.png)](src/year2025/day4/day4.go)
2121
[Day 5: Cafeteria](https://adventofcode.com/2025/day/5) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day5/day5.rs) [![Go](./scripts/assets/go.png)](src/year2025/day5/day5.go)
22-
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day6/day6.rs) [![Python](./scripts/assets/python.png)](src/year2025/day6/day6.py)
22+
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day6/day6.rs) [![Rust](./scripts/assets/rust.png)](src/year2025/day6/day6_declarative.rs) [![Python](./scripts/assets/python.png)](src/year2025/day6/day6.py)
23+
[Day 7: Laboratories](https://adventofcode.com/2025/day/7) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day7/day7.rs) [![Go](./scripts/assets/go.png)](src/year2025/day7/day7.go)
2324

2425
## All years
2526

2627
Calendar | Solutions | Stars | Rust | Python | 🎁
2728
-------- | --------- | ----- | ---- | ------ | --
28-
[Advent of Code 2025](https://adventofcode.com/2025) | [Solutions](src/year2025/README.md) | 12⭐ | 6 | 5 |
29+
[Advent of Code 2025](https://adventofcode.com/2025) | [Solutions](src/year2025/README.md) | 14⭐ | 7 | 5 |
2930
[Advent of Code 2024](https://adventofcode.com/2024) | [Solutions](src/year2024/README.md) | 50⭐ | 25 | 11 | 3
3031
[Advent of Code 2023](https://adventofcode.com/2023) | [Solutions](src/year2023/README.md) | 50⭐ | 25 | 10 | 2
3132
[Advent of Code 2022](https://adventofcode.com/2022) | [Solutions](src/year2022/README.md) | 50⭐ | 25 | 18 | 1

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,5 @@ make_year!(year2024
127127
);
128128

129129
make_year!(year2025
130-
day1,day2,day3,day4,day5,day6
130+
day1,day2,day3,day4,day5,day6,day7
131131
);

src/year2025/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Advent of Code in Rust 🦀
22

33
![AoC2025](https://img.shields.io/badge/Advent_of_Code-2025-8A2BE2)
4-
![Stars: 12](https://img.shields.io/badge/Stars-12⭐-blue)
5-
![Rust: 6](https://img.shields.io/badge/Rust-6-cyan?logo=Rust)
4+
![Stars: 14](https://img.shields.io/badge/Stars-14⭐-blue)
5+
![Rust: 8](https://img.shields.io/badge/Rust-8-cyan?logo=Rust)
66
![Python: 5](https://img.shields.io/badge/Python-5-cyan?logo=Python)
77

8-
## 2025 ([Calendar](https://adventofcode.com/2025)) ([Solutions](./)) : 12
8+
## 2025 ([Calendar](https://adventofcode.com/2025)) ([Solutions](./)) : 14
99

1010
Puzzle | Stars | Languages
1111
----------------------------------------------------------------- | ----- | -----------
@@ -14,4 +14,5 @@ Puzzle | Stars | Lang
1414
[Day 3: Lobby](https://adventofcode.com/2025/day/3) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day3/day3.rs) [![Python](../../scripts/assets/python.png)](day3/day3.py) [![Go](../../scripts/assets/go.png)](day3/day3.go)
1515
[Day 4: Printing Department](https://adventofcode.com/2025/day/4) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day4/day4.rs) [![Python](../../scripts/assets/python.png)](day4/day4.py) [![Go](../../scripts/assets/go.png)](day4/day4.go)
1616
[Day 5: Cafeteria](https://adventofcode.com/2025/day/5) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day5/day5.rs) [![Go](../../scripts/assets/go.png)](day5/day5.go)
17-
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day6/day6.rs) [![Python](../../scripts/assets/python.png)](day6/day6.py)
17+
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day6/day6.rs) [![Rust](../../scripts/assets/rust.png)](day6/day6_declarative.rs) [![Python](../../scripts/assets/python.png)](day6/day6.py)
18+
[Day 7: Laboratories](https://adventofcode.com/2025/day/7) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day7/day7.rs) [![Go](../../scripts/assets/go.png)](day7/day7.go)

src/year2025/day7/day7.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// [Day 7: Laboratories](https://adventofcode.com/2025/day/7)
2+
3+
package main
4+
5+
import (
6+
"fmt"
7+
"log"
8+
"os"
9+
"strings"
10+
"time"
11+
)
12+
13+
// Coord represents a position in the grid.
14+
type Coord struct {
15+
X, Y int
16+
}
17+
18+
// Grid is a simple 2D grid of bytes.
19+
type Grid struct {
20+
data [][]byte
21+
width int
22+
height int
23+
}
24+
25+
// Parse creates a grid from multiline input.
26+
func Parse(input []byte) *Grid {
27+
28+
var data [][]byte
29+
start := 0
30+
height := 0
31+
width := 0
32+
33+
for i, b := range input {
34+
if b == '\n' {
35+
if i-start > width {
36+
width = i - start
37+
}
38+
height += 1
39+
data = append(data, input[start:i])
40+
start = i + 1
41+
}
42+
}
43+
44+
return &Grid{
45+
data: data,
46+
width: width,
47+
height: height,
48+
}
49+
}
50+
51+
// At returns the cell value at (x, y).
52+
func (g *Grid) At(x, y int) byte {
53+
return g.data[y][x]
54+
}
55+
56+
// Width returns grid width.
57+
func (g *Grid) Width() int {
58+
return g.width
59+
}
60+
61+
// Height returns grid height.
62+
func (g *Grid) Height() int {
63+
return g.height
64+
}
65+
66+
const (
67+
START = byte('S')
68+
SPLITTER = byte('^')
69+
)
70+
71+
// puzzle is the input data.
72+
type puzzle struct {
73+
grid *Grid // grid is the diagram of the tachyon manifold.
74+
start Coord // start is the position where the tachyon beam enters the manifold.
75+
}
76+
77+
// newPuzzle reads the manifold diagram and start position from a multiline input.
78+
func newPuzzle(data []byte) *puzzle {
79+
grid := Parse(data)
80+
81+
var start Coord
82+
found := false
83+
84+
for y := 0; y < grid.Height(); y++ {
85+
for x := 0; x < grid.Width(); x++ {
86+
if grid.At(x, y) == START {
87+
start = Coord{X: x, Y: y}
88+
found = true
89+
break
90+
}
91+
}
92+
if found {
93+
break
94+
}
95+
}
96+
97+
return &puzzle{grid: grid, start: start}
98+
}
99+
100+
// part1 counts times the beam will be split.
101+
func (p *puzzle) part1() uint64 {
102+
var splits uint64
103+
104+
beams := map[int]struct{}{}
105+
beams[p.start.X] = struct{}{}
106+
107+
for y := p.start.Y; y < p.grid.Height(); y++ {
108+
nextBeams := map[int]struct{}{}
109+
110+
for x := range beams {
111+
if p.grid.At(x, y) == SPLITTER {
112+
splits++
113+
114+
if x > 0 {
115+
nextBeams[x-1] = struct{}{}
116+
}
117+
if x < p.grid.Width()-1 {
118+
nextBeams[x+1] = struct{}{}
119+
}
120+
} else {
121+
nextBeams[x] = struct{}{}
122+
}
123+
}
124+
125+
beams = nextBeams
126+
}
127+
128+
return splits
129+
}
130+
131+
// part2 count the timelines for a single tachyon particle.
132+
func (p *puzzle) part2() uint64 {
133+
134+
timelines := map[int]uint64{}
135+
timelines[p.start.X] = 1
136+
137+
for y := p.start.Y; y < p.grid.Height(); y++ {
138+
nextTimelines := map[int]uint64{}
139+
140+
for x, ways := range timelines {
141+
if p.grid.At(x, y) == SPLITTER {
142+
if x > 0 {
143+
nextTimelines[x-1] += ways
144+
}
145+
if x < p.grid.Width()-1 {
146+
nextTimelines[x+1] += ways
147+
}
148+
} else {
149+
nextTimelines[x] += ways
150+
}
151+
}
152+
153+
timelines = nextTimelines
154+
}
155+
156+
var total uint64
157+
for _, v := range timelines {
158+
total += v
159+
}
160+
161+
return total
162+
}
163+
164+
// solve solves parts one and two of the puzzle.
165+
func solve(data []byte) (uint64, uint64) {
166+
167+
puzzle := newPuzzle(data)
168+
169+
return puzzle.part1(), puzzle.part2()
170+
}
171+
172+
func main() {
173+
filename := "input.txt"
174+
elapsed := false
175+
176+
for i := 1; i < len(os.Args); i++ {
177+
arg := os.Args[i]
178+
if arg == "--elapsed" {
179+
elapsed = true
180+
} else if !strings.HasPrefix(arg, "-") {
181+
filename = arg
182+
}
183+
}
184+
185+
data, err := os.ReadFile(filename)
186+
if err != nil {
187+
log.Fatal(err)
188+
}
189+
190+
if elapsed {
191+
start := time.Now()
192+
result1, result2 := solve(data)
193+
duration := time.Since(start)
194+
195+
fmt.Println(result1)
196+
fmt.Println(result2)
197+
fmt.Printf("elapsed: %f ms\n", duration.Seconds()*1000.0)
198+
} else {
199+
result1, result2 := solve(data)
200+
201+
fmt.Println(result1)
202+
fmt.Println(result2)
203+
}
204+
}

src/year2025/day7/day7.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//! [Day 7: Laboratories](https://adventofcode.com/2025/day/7)
2+
3+
use rustc_hash::{FxHashMap, FxHashSet};
4+
5+
const START: u8 = b'S';
6+
const SPLITTER: u8 = b'^';
7+
8+
struct Puzzle {
9+
grid: aoc::Grid<u8>,
10+
start: aoc::Coord,
11+
}
12+
13+
impl Puzzle {
14+
/// Initialize from the puzzle input.
15+
fn new(data: &str) -> Self {
16+
let grid = aoc::Grid::<u8>::parse(data);
17+
18+
let start = grid.iter().find(|&(_, v)| v == &START).unwrap().0;
19+
20+
Self { grid, start }
21+
}
22+
23+
/// Solve part one.
24+
fn part1(&self) -> u64 {
25+
let mut splits = 0;
26+
27+
let mut beams = FxHashSet::default();
28+
29+
beams.insert(self.start.x);
30+
31+
for y in self.start.y..self.grid.height() {
32+
let mut next_beams = FxHashSet::default();
33+
34+
for x in beams {
35+
if self.grid[(x, y)] == SPLITTER {
36+
splits += 1;
37+
38+
if x > 0 {
39+
next_beams.insert(x - 1);
40+
}
41+
42+
if x < self.grid.width() - 1 {
43+
next_beams.insert(x + 1);
44+
}
45+
} else {
46+
next_beams.insert(x);
47+
}
48+
}
49+
50+
beams = next_beams;
51+
}
52+
53+
splits
54+
}
55+
56+
/// Solve part two.
57+
fn part2(&self) -> u64 {
58+
let mut timelines = FxHashMap::default();
59+
60+
timelines.insert(self.start.x, 1);
61+
62+
for y in self.start.y..self.grid.height() {
63+
let mut next_timelines = FxHashMap::default();
64+
65+
for (&x, ways) in &timelines {
66+
if self.grid[(x, y)] == SPLITTER {
67+
if x > 0 {
68+
*next_timelines.entry(x - 1).or_insert(0) += ways;
69+
}
70+
71+
if x < self.grid.width() - 1 {
72+
*next_timelines.entry(x + 1).or_insert(0) += ways;
73+
}
74+
} else {
75+
*next_timelines.entry(x).or_insert(0) += ways;
76+
}
77+
}
78+
79+
timelines = next_timelines;
80+
}
81+
82+
timelines.into_values().sum()
83+
}
84+
}
85+
86+
/// # Panics
87+
#[must_use]
88+
pub fn solve(data: &str) -> (u64, u64) {
89+
let puzzle = Puzzle::new(data);
90+
(puzzle.part1(), puzzle.part2())
91+
}
92+
93+
pub fn main() {
94+
let args = aoc::parse_args();
95+
args.run(solve);
96+
}
97+
98+
#[cfg(test)]
99+
mod test {
100+
use super::*;
101+
102+
const TEST_INPUT: &str = include_str!("test.txt");
103+
104+
#[test]
105+
fn part1() {
106+
let puzzle = Puzzle::new(TEST_INPUT);
107+
assert_eq!(puzzle.part1(), 21);
108+
}
109+
110+
#[test]
111+
fn part2() {
112+
let puzzle = Puzzle::new(TEST_INPUT);
113+
assert_eq!(puzzle.part2(), 40);
114+
}
115+
}

0 commit comments

Comments
 (0)