mirror of
https://github.com/kristoferssolo/Advent-of-Code.git
synced 2025-12-31 13:42:32 +00:00
Compare commits
31 Commits
20d6c6bff0
...
185c082764
| Author | SHA1 | Date | |
|---|---|---|---|
| 185c082764 | |||
| ea4cfa28eb | |||
| a2ccf9b271 | |||
| d236d75bf4 | |||
| d54f976f07 | |||
| 51ca0e1215 | |||
| 18dd729a49 | |||
| 9d78fedb86 | |||
| ecdd78ed14 | |||
| 838f24ea5d | |||
| 056a1a1208 | |||
| 43c57b58df | |||
| 6cac42e2df | |||
| 41d1feffc2 | |||
| 770d09d42b | |||
| 92ef093b85 | |||
| 0ab0cf8bf8 | |||
| 3deee94f8a | |||
| 71ac0a4c35 | |||
| 4a259655e2 | |||
| 3cfc40324c | |||
| 6330105eeb | |||
| 2ae6b63d6d | |||
| 3449dba83e | |||
| 007a9fabd5 | |||
| 05863f1bf8 | |||
| c62b63462c | |||
| 1c4b2188f1 | |||
| 1dcdb86bed | |||
| 2d34e14f7d | |||
| dc25916f7b |
3
2025/.gitignore
vendored
Normal file
3
2025/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
.env
|
||||||
|
input*.txt
|
||||||
1038
2025/Cargo.lock
generated
Normal file
1038
2025/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
2025/Cargo.toml
Normal file
50
2025/Cargo.toml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"day-*",
|
||||||
|
"day-01",
|
||||||
|
"day-02",
|
||||||
|
"day-03",
|
||||||
|
"day-04",
|
||||||
|
"day-05",
|
||||||
|
"day-06",
|
||||||
|
"day-07",
|
||||||
|
"day-08",
|
||||||
|
"day-09",
|
||||||
|
"day-10",
|
||||||
|
"day-11",
|
||||||
|
]
|
||||||
|
default-members = ["day-*"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
divan = "0.1"
|
||||||
|
glam = "0.30"
|
||||||
|
itertools = "0.14"
|
||||||
|
nom = "8.0"
|
||||||
|
nom-supreme = "0.8"
|
||||||
|
nom_locate = "5.0"
|
||||||
|
rayon = "1.11"
|
||||||
|
rstest = "0.26"
|
||||||
|
rstest_reuse = "0.7"
|
||||||
|
thiserror = "2.0"
|
||||||
|
tracing = "0.1"
|
||||||
|
|
||||||
|
[workspace.dependencies.miette]
|
||||||
|
version = "7.6"
|
||||||
|
features = ["fancy"]
|
||||||
|
|
||||||
|
[workspace.dependencies.test-log]
|
||||||
|
version = "0.2"
|
||||||
|
features = ["trace"]
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[workspace.dependencies.tracing-subscriber]
|
||||||
|
version = "0.3"
|
||||||
|
features = [
|
||||||
|
"fmt",
|
||||||
|
"env-filter",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
nursery = "warn"
|
||||||
|
pedantic = "warn"
|
||||||
113
2025/benchmarks.txt
Normal file
113
2025/benchmarks.txt
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
running 2 tests
|
||||||
|
ii
|
||||||
|
test result: ok. 0 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
day_01_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 102.2 µs │ 158 µs │ 104.7 µs │ 107.5 µs │ 100 │ 100
|
||||||
|
╰─ part2 112.4 µs │ 142.1 µs │ 113.3 µs │ 114.5 µs │ 100 │ 100
|
||||||
|
|
||||||
|
|
||||||
|
running 22 tests
|
||||||
|
iiiiiiiiiiiiiiiiiiiiii
|
||||||
|
test result: ok. 0 passed; 0 failed; 22 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
day_02_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 95.12 ms │ 98.88 ms │ 95.83 ms │ 96.01 ms │ 100 │ 100
|
||||||
|
╰─ part2 94.67 ms │ 99.18 ms │ 95.81 ms │ 96.01 ms │ 100 │ 100
|
||||||
|
|
||||||
|
|
||||||
|
running 2 tests
|
||||||
|
ii
|
||||||
|
test result: ok. 0 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
day_03_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 199.8 µs │ 227.2 µs │ 211.5 µs │ 212 µs │ 100 │ 100
|
||||||
|
╰─ part2 183.5 µs │ 315.2 µs │ 199.9 µs │ 201.5 µs │ 100 │ 100
|
||||||
|
|
||||||
|
|
||||||
|
running 2 tests
|
||||||
|
ii
|
||||||
|
test result: ok. 0 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
day_04_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 315 µs │ 328.9 µs │ 322.7 µs │ 322.6 µs │ 100 │ 100
|
||||||
|
╰─ part2 13.57 ms │ 15.01 ms │ 14.44 ms │ 14.45 ms │ 100 │ 100
|
||||||
|
|
||||||
|
|
||||||
|
running 2 tests
|
||||||
|
ii
|
||||||
|
test result: ok. 0 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
day_05_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 54.38 µs │ 111.6 µs │ 57.5 µs │ 58.85 µs │ 100 │ 100
|
||||||
|
╰─ part2 6.472 µs │ 12.59 µs │ 6.631 µs │ 6.707 µs │ 100 │ 100
|
||||||
|
|
||||||
|
|
||||||
|
running 2 tests
|
||||||
|
ii
|
||||||
|
test result: ok. 0 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
|
||||||
|
|
||||||
|
day_06_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 46.83 µs │ 94.14 µs │ 47.75 µs │ 49.44 µs │ 100 │ 100
|
||||||
|
╰─ part2
|
||||||
27
2025/daily-template/Cargo.toml
Normal file
27
2025/daily-template/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "{{project-name}}"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "{{project-name}}-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
15
2025/daily-template/benches/benchmarks.rs
Normal file
15
2025/daily-template/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use {{crate_name}}::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
0
2025/daily-template/cargo-generate.toml
Normal file
0
2025/daily-template/cargo-generate.toml
Normal file
12
2025/daily-template/src/bin/part1.rs
Normal file
12
2025/daily-template/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use {{crate_name}}::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/daily-template/src/bin/part2.rs
Normal file
12
2025/daily-template/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use {{crate_name}}::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/daily-template/src/lib.rs
Normal file
2
2025/daily-template/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
22
2025/daily-template/src/part1.rs
Normal file
22
2025/daily-template/src/part1.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use miette::miette;
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
todo!("day xx - part 1");
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "";
|
||||||
|
todo!("haven't built test yet");
|
||||||
|
let result = 0;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
23
2025/daily-template/src/part2.rs
Normal file
23
2025/daily-template/src/part2.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use miette::miette;
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
todo!("day xx - part 2");
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "";
|
||||||
|
todo!("haven't built test yet");
|
||||||
|
let result = 0;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-01/Cargo.toml
Normal file
27
2025/day-01/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-01"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-01-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-01/bench.txt
Normal file
4
2025/day-01/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_01_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 128.6 µs │ 641 µs │ 129 µs │ 146.8 µs │ 100 │ 100
|
||||||
|
╰─ part2 138.3 µs │ 471.2 µs │ 139.5 µs │ 161.4 µs │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-01/benches/benchmarks.rs
Normal file
15
2025/day-01/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_01::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-01/src/bin/part1.rs
Normal file
12
2025/day-01/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_01::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-01/src/bin/part2.rs
Normal file
12
2025/day-01/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_01::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-01/src/lib.rs
Normal file
2
2025/day-01/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
106
2025/day-01/src/part1.rs
Normal file
106
2025/day-01/src/part1.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use miette::Result;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
enum Direction {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Direction {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().trim() {
|
||||||
|
"l" => Ok(Self::Left),
|
||||||
|
"r" => Ok(Self::Right),
|
||||||
|
_ => Err("Wrong value".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Rotation {
|
||||||
|
amount: i32,
|
||||||
|
direction: Direction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rotation {
|
||||||
|
const fn right(self, dial: i32) -> i32 {
|
||||||
|
(dial + self.amount).rem_euclid(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn left(self, dial: i32) -> i32 {
|
||||||
|
(dial - self.amount).rem_euclid(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Rotation {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
let (direction_str, amount_str) = trimmed.split_at(1);
|
||||||
|
let direction = direction_str.parse()?;
|
||||||
|
let amount = amount_str.parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(Self { amount, direction })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Sequence {
|
||||||
|
rotations: Vec<Rotation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Sequence {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let rotations = s
|
||||||
|
.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.parse::<Rotation>().unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(Self { rotations })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> Result<usize> {
|
||||||
|
let sequence = input.parse::<Sequence>().unwrap();
|
||||||
|
let mut count = 0;
|
||||||
|
sequence.rotations.iter().fold(50, |acc, rotation| {
|
||||||
|
let number = match rotation.direction {
|
||||||
|
Direction::Left => rotation.left(acc),
|
||||||
|
Direction::Right => rotation.right(acc),
|
||||||
|
};
|
||||||
|
if number == 0 {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
number
|
||||||
|
});
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> Result<()> {
|
||||||
|
let input = "L68
|
||||||
|
L30
|
||||||
|
R48
|
||||||
|
L5
|
||||||
|
R60
|
||||||
|
L55
|
||||||
|
L1
|
||||||
|
L99
|
||||||
|
R14
|
||||||
|
L82
|
||||||
|
";
|
||||||
|
let result = 3;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
124
2025/day-01/src/part2.rs
Normal file
124
2025/day-01/src/part2.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use miette::Result;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
enum Direction {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Direction {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().trim() {
|
||||||
|
"l" => Ok(Self::Left),
|
||||||
|
"r" => Ok(Self::Right),
|
||||||
|
_ => Err("Wrong value".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Rotation {
|
||||||
|
amount: i32,
|
||||||
|
direction: Direction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Rotation {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
let (direction_str, amount_str) = trimmed.split_at(1);
|
||||||
|
let direction = direction_str.parse()?;
|
||||||
|
let amount = amount_str.parse::<i32>().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(Self { amount, direction })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Sequence {
|
||||||
|
rotations: Vec<Rotation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Sequence {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let rotations = s
|
||||||
|
.trim()
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.parse::<Rotation>().unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(Self { rotations })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> Result<usize> {
|
||||||
|
let sequence = input.parse::<Sequence>().unwrap();
|
||||||
|
let (_, total_zeros) = sequence
|
||||||
|
.rotations
|
||||||
|
.iter()
|
||||||
|
.fold((50, 0), |(dial, total), rotation| {
|
||||||
|
let new_dial = match rotation.direction {
|
||||||
|
Direction::Left => (dial - rotation.amount).rem_euclid(100),
|
||||||
|
Direction::Right => (dial + rotation.amount).rem_euclid(100),
|
||||||
|
};
|
||||||
|
|
||||||
|
let zeros_crossed = count_zeros_crossed(dial, rotation.amount, rotation.direction);
|
||||||
|
let new_total = total + zeros_crossed;
|
||||||
|
(new_dial, new_total)
|
||||||
|
});
|
||||||
|
Ok(total_zeros.try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn count_zeros_crossed(pos: i32, amount: i32, direction: Direction) -> i32 {
|
||||||
|
match direction {
|
||||||
|
Direction::Left => {
|
||||||
|
if pos == 0 {
|
||||||
|
amount / 100
|
||||||
|
} else if amount >= pos {
|
||||||
|
1 + (amount - pos) / 100
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Direction::Right => {
|
||||||
|
if pos == 0 {
|
||||||
|
amount / 100
|
||||||
|
} else {
|
||||||
|
let target = 100 - pos;
|
||||||
|
if amount >= target {
|
||||||
|
1 + (amount - target) / 100
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> Result<()> {
|
||||||
|
let input = "L68
|
||||||
|
L30
|
||||||
|
R48
|
||||||
|
L5
|
||||||
|
R60
|
||||||
|
L55
|
||||||
|
L1
|
||||||
|
L99
|
||||||
|
R14
|
||||||
|
L82
|
||||||
|
";
|
||||||
|
let result = 6;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-02/Cargo.toml
Normal file
27
2025/day-02/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-02"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-02-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-02/bench.txt
Normal file
4
2025/day-02/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_02_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 131.4 ms │ 141.6 ms │ 134.1 ms │ 134.5 ms │ 100 │ 100
|
||||||
|
╰─ part2 134.5 ms │ 141.5 ms │ 137.7 ms │ 137.6 ms │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-02/benches/benchmarks.rs
Normal file
15
2025/day-02/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_02::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-02/src/bin/part1.rs
Normal file
12
2025/day-02/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_02::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-02/src/bin/part2.rs
Normal file
12
2025/day-02/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_02::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-02/src/lib.rs
Normal file
2
2025/day-02/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
102
2025/day-02/src/part1.rs
Normal file
102
2025/day-02/src/part1.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use miette::Result;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Id(usize);
|
||||||
|
|
||||||
|
impl FromStr for Id {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let val = s.parse::<usize>().map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Range {
|
||||||
|
start: Id,
|
||||||
|
end: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Range {
|
||||||
|
fn find_invalid(&self) -> Vec<Id> {
|
||||||
|
(self.start.0..=self.end.0)
|
||||||
|
.filter_map(|x| {
|
||||||
|
if has_repeating_sequence(x) {
|
||||||
|
Some(Id(x))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_repeating_sequence(num: usize) -> bool {
|
||||||
|
let s = num.to_string();
|
||||||
|
let len = s.len();
|
||||||
|
(1..=len / 2).any(|pattern_len| {
|
||||||
|
let repeats = len / pattern_len;
|
||||||
|
repeats >= 2
|
||||||
|
&& len.is_multiple_of(pattern_len)
|
||||||
|
&& s[0..pattern_len].repeat(len / pattern_len) == s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Range {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let mut trimmed = s.trim().split('-');
|
||||||
|
let start = trimmed.next().unwrap_or_default().parse()?;
|
||||||
|
let end = trimmed.next_back().unwrap_or_default().parse()?;
|
||||||
|
Ok(Self { start, end })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> Result<usize> {
|
||||||
|
let result = input
|
||||||
|
.trim()
|
||||||
|
.split(',')
|
||||||
|
.map(Range::from_str)
|
||||||
|
.flat_map(|range| range.unwrap().find_invalid())
|
||||||
|
.map(|x| x.0)
|
||||||
|
.sum();
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> Result<()> {
|
||||||
|
let input = "11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124";
|
||||||
|
let result = 1_227_775_554;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(11)]
|
||||||
|
#[case(22)]
|
||||||
|
#[case(1010)]
|
||||||
|
#[case(1_188_511_885)]
|
||||||
|
#[case(222_222)]
|
||||||
|
#[case(446_446)]
|
||||||
|
#[case(38_593_859)]
|
||||||
|
fn repeating(#[case] num: usize) {
|
||||||
|
assert!(has_repeating_sequence(num));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(111)]
|
||||||
|
#[case(222)]
|
||||||
|
#[case(222_222_222)]
|
||||||
|
fn not_repeating(#[case] num: usize) {
|
||||||
|
assert!(!has_repeating_sequence(num));
|
||||||
|
}
|
||||||
|
}
|
||||||
94
2025/day-02/src/part2.rs
Normal file
94
2025/day-02/src/part2.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
use miette::Result;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Id(usize);
|
||||||
|
|
||||||
|
impl FromStr for Id {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let val = s.parse::<usize>().map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Range {
|
||||||
|
start: Id,
|
||||||
|
end: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Range {
|
||||||
|
fn find_invalid(&self) -> Vec<Id> {
|
||||||
|
(self.start.0..=self.end.0)
|
||||||
|
.filter_map(|x| {
|
||||||
|
if has_repeating_sequence(x) {
|
||||||
|
Some(Id(x))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_repeating_sequence(num: usize) -> bool {
|
||||||
|
let s = num.to_string();
|
||||||
|
let len = s.len();
|
||||||
|
(1..=len / 2).any(|pattern_len| {
|
||||||
|
len.is_multiple_of(pattern_len) && s[0..pattern_len].repeat(len / pattern_len) == s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Range {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let mut trimmed = s.trim().split('-');
|
||||||
|
let start = trimmed.next().unwrap_or_default().parse()?;
|
||||||
|
let end = trimmed.next_back().unwrap_or_default().parse()?;
|
||||||
|
Ok(Self { start, end })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> Result<usize> {
|
||||||
|
let result = input
|
||||||
|
.trim()
|
||||||
|
.split(',')
|
||||||
|
.map(Range::from_str)
|
||||||
|
.flat_map(|range| range.unwrap().find_invalid())
|
||||||
|
.map(|x| x.0)
|
||||||
|
.sum();
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> Result<()> {
|
||||||
|
let input = "11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124";
|
||||||
|
let result = 4_174_379_265;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(11)]
|
||||||
|
#[case(22)]
|
||||||
|
#[case(1010)]
|
||||||
|
#[case(1_188_511_885)]
|
||||||
|
#[case(222_222)]
|
||||||
|
#[case(446_446)]
|
||||||
|
#[case(38_593_859)]
|
||||||
|
#[case(111)]
|
||||||
|
#[case(222)]
|
||||||
|
#[case(222_222_222)]
|
||||||
|
fn repeating(#[case] num: usize) {
|
||||||
|
assert!(has_repeating_sequence(num));
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-03/Cargo.toml
Normal file
27
2025/day-03/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-03"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-03-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-03/bench.txt
Normal file
4
2025/day-03/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_03_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 308.2 µs │ 565.8 µs │ 316.1 µs │ 323.9 µs │ 100 │ 100
|
||||||
|
╰─ part2 328.5 µs │ 1.456 ms │ 336 µs │ 353 µs │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-03/benches/benchmarks.rs
Normal file
15
2025/day-03/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_03::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-03/src/bin/part1.rs
Normal file
12
2025/day-03/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_03::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-03/src/bin/part2.rs
Normal file
12
2025/day-03/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_03::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-03/src/lib.rs
Normal file
2
2025/day-03/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
90
2025/day-03/src/part1.rs
Normal file
90
2025/day-03/src/part1.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use std::{ops::Add, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct Joltage(u8);
|
||||||
|
|
||||||
|
impl Add for Joltage {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Self(self.0 * 10 + rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for &Joltage {
|
||||||
|
type Output = Joltage;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Joltage(self.0 * 10 + rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Joltage> for usize {
|
||||||
|
fn from(value: Joltage) -> Self {
|
||||||
|
Self::from(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for Joltage {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(ch: char) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let s = ch.to_string();
|
||||||
|
let value = s.parse::<u8>().map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Bank(Vec<Joltage>);
|
||||||
|
|
||||||
|
impl Bank {
|
||||||
|
fn get_max_value(&self) -> usize {
|
||||||
|
let mut clone = self.0.clone();
|
||||||
|
clone.truncate(self.0.len() - 1);
|
||||||
|
let max1 = clone.iter().max().unwrap();
|
||||||
|
let (pos1, _) = self.0.iter().find_position(|&x| x == max1).unwrap();
|
||||||
|
let list = self.0.clone().split_off(pos1 + 1);
|
||||||
|
let max2 = list.iter().max().unwrap();
|
||||||
|
(max1 + max2).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Bank {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let bank = s
|
||||||
|
.trim()
|
||||||
|
.chars()
|
||||||
|
.map(Joltage::try_from)
|
||||||
|
.collect::<Result<Vec<_>, String>>()?;
|
||||||
|
|
||||||
|
Ok(Self(bank))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let banks = input
|
||||||
|
.lines()
|
||||||
|
.map(|line| Bank::from_str(line).unwrap())
|
||||||
|
.map(|bank| bank.get_max_value())
|
||||||
|
.sum();
|
||||||
|
Ok(banks)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "987654321111111
|
||||||
|
811111111111119
|
||||||
|
234234234234278
|
||||||
|
818181911112111
|
||||||
|
";
|
||||||
|
let result = 357;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
109
2025/day-03/src/part2.rs
Normal file
109
2025/day-03/src/part2.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use std::{ops::Add, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct Joltage(usize);
|
||||||
|
|
||||||
|
impl Add for Joltage {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Self(self.0 * 10 + rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for &Joltage {
|
||||||
|
type Output = Joltage;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Joltage(self.0 * 10 + rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Joltage> for usize {
|
||||||
|
fn from(value: Joltage) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for Joltage {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(ch: char) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let s = ch.to_string();
|
||||||
|
let value = s.parse::<usize>().map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Bank(Vec<Joltage>);
|
||||||
|
|
||||||
|
impl Bank {
|
||||||
|
const LEN: usize = 12;
|
||||||
|
|
||||||
|
fn get_max_value(&self) -> usize {
|
||||||
|
if self.0.len() < Self::LEN {
|
||||||
|
return self.0.iter().map(|x| x.0).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = Joltage(0);
|
||||||
|
let mut start = 0;
|
||||||
|
let mut remaining = Self::LEN;
|
||||||
|
|
||||||
|
while remaining > 0 {
|
||||||
|
let search_end = self.0.len() - remaining + 1;
|
||||||
|
|
||||||
|
let mut max_joltage = self.0[start];
|
||||||
|
let mut max_pos = start;
|
||||||
|
|
||||||
|
for i in start..search_end {
|
||||||
|
if self.0[i] > max_joltage {
|
||||||
|
max_joltage = self.0[i];
|
||||||
|
max_pos = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = result + max_joltage;
|
||||||
|
start = max_pos + 1;
|
||||||
|
remaining -= 1;
|
||||||
|
}
|
||||||
|
result.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Bank {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let bank = s
|
||||||
|
.trim()
|
||||||
|
.chars()
|
||||||
|
.map(Joltage::try_from)
|
||||||
|
.collect::<Result<Vec<_>, String>>()?;
|
||||||
|
|
||||||
|
Ok(Self(bank))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let banks = input
|
||||||
|
.lines()
|
||||||
|
.map(|line| Bank::from_str(line).unwrap())
|
||||||
|
.map(|bank| bank.get_max_value())
|
||||||
|
.sum();
|
||||||
|
Ok(banks)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "987654321111111
|
||||||
|
811111111111119
|
||||||
|
234234234234278
|
||||||
|
818181911112111
|
||||||
|
";
|
||||||
|
let result = 3_121_910_778_619;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-04/Cargo.toml
Normal file
27
2025/day-04/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-04"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-04-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-04/bench.txt
Normal file
4
2025/day-04/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_04_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 497.7 µs │ 571.3 µs │ 501.7 µs │ 503.6 µs │ 100 │ 100
|
||||||
|
╰─ part2 22.84 ms │ 28.42 ms │ 22.91 ms │ 23.24 ms │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-04/benches/benchmarks.rs
Normal file
15
2025/day-04/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_04::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-04/src/bin/part1.rs
Normal file
12
2025/day-04/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_04::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-04/src/bin/part2.rs
Normal file
12
2025/day-04/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_04::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-04/src/lib.rs
Normal file
2
2025/day-04/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
134
2025/day-04/src/part1.rs
Normal file
134
2025/day-04/src/part1.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use std::{convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Space {
|
||||||
|
Roll,
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<char> for Space {
|
||||||
|
fn from(ch: char) -> Self {
|
||||||
|
match ch {
|
||||||
|
'@' => Self::Roll,
|
||||||
|
_ => Self::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Row(Vec<Space>);
|
||||||
|
|
||||||
|
impl FromStr for Row {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let row = s.chars().map(Space::from).collect();
|
||||||
|
Ok(Self(row))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Grid(Vec<Row>);
|
||||||
|
|
||||||
|
impl FromStr for Grid {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let grid = s
|
||||||
|
.lines()
|
||||||
|
.map(Row::from_str)
|
||||||
|
.collect::<Result<Vec<_>, Infallible>>()
|
||||||
|
.unwrap();
|
||||||
|
Ok(Self(grid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Coords {
|
||||||
|
x: isize,
|
||||||
|
y: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coords {
|
||||||
|
const fn new(x: isize, y: isize) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn offset(&self, coords: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x + coords.x,
|
||||||
|
y: self.y + coords.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
fn find_accessible(&self) -> usize {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(x, row)| row.0.iter().enumerate().map(move |(y, _)| (x, y)))
|
||||||
|
.filter(|(x, y)| {
|
||||||
|
let coords = Coords::new((*x).try_into().unwrap(), (*y).try_into().unwrap());
|
||||||
|
let adjacent = self.get_adjacent(&coords);
|
||||||
|
adjacent < 4 && matches!(self.get(&coords), Space::Roll)
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_adjacent(&self, coords: &Coords) -> usize {
|
||||||
|
let max_x = self.0.len() - 1;
|
||||||
|
let max_y = self.0[0].0.len() - 1;
|
||||||
|
|
||||||
|
(-1..=1)
|
||||||
|
.flat_map(|x| (-1..=1).map(move |y| (x, y)))
|
||||||
|
.filter(|(x, y)| {
|
||||||
|
!(*x == 0 && *y == 0)
|
||||||
|
&& !((coords.x == 0 && *x == -1)
|
||||||
|
|| (coords.y == 0 && *y == -1)
|
||||||
|
|| (usize::try_from(coords.x).unwrap() == max_x && *x == 1)
|
||||||
|
|| (usize::try_from(coords.y).unwrap() == max_y && *y == 1))
|
||||||
|
})
|
||||||
|
.filter(|(x, y)| {
|
||||||
|
let coord = coords.offset(&Coords::new(
|
||||||
|
(*x).try_into().unwrap(),
|
||||||
|
(*y).try_into().unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
|
matches!(self.get(&coord), Space::Roll)
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, coords: &Coords) -> Space {
|
||||||
|
self.0[usize::try_from(coords.x).unwrap()].0[usize::try_from(coords.y).unwrap()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let grid = Grid::from_str(input)?;
|
||||||
|
Ok(grid.find_accessible())
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "..@@.@@@@.
|
||||||
|
@@@.@.@.@@
|
||||||
|
@@@@@.@.@@
|
||||||
|
@.@@@@..@.
|
||||||
|
@@.@@@@.@@
|
||||||
|
.@@@@@@@.@
|
||||||
|
.@.@.@.@@@
|
||||||
|
@.@@@.@@@@
|
||||||
|
.@@@@@@@@.
|
||||||
|
@.@.@@@.@.
|
||||||
|
";
|
||||||
|
let result = 13;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
146
2025/day-04/src/part2.rs
Normal file
146
2025/day-04/src/part2.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use std::{convert::Infallible, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Space {
|
||||||
|
Roll,
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<char> for Space {
|
||||||
|
fn from(ch: char) -> Self {
|
||||||
|
match ch {
|
||||||
|
'@' => Self::Roll,
|
||||||
|
_ => Self::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Row(Vec<Space>);
|
||||||
|
|
||||||
|
impl FromStr for Row {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let row = s.chars().map(Space::from).collect();
|
||||||
|
Ok(Self(row))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Grid(Vec<Row>);
|
||||||
|
|
||||||
|
impl FromStr for Grid {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let grid = s
|
||||||
|
.lines()
|
||||||
|
.map(Row::from_str)
|
||||||
|
.collect::<Result<Vec<_>, Infallible>>()
|
||||||
|
.unwrap();
|
||||||
|
Ok(Self(grid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Coords {
|
||||||
|
x: isize,
|
||||||
|
y: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coords {
|
||||||
|
const fn new(x: isize, y: isize) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn offset(&self, coords: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x + coords.x,
|
||||||
|
y: self.y + coords.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
fn find_accessible(&mut self) -> usize {
|
||||||
|
let mut total_removed = 0;
|
||||||
|
loop {
|
||||||
|
let accessible = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(x, row)| row.0.iter().enumerate().map(move |(y, _)| (x, y)))
|
||||||
|
.filter(|(x, y)| {
|
||||||
|
let coords = Coords::new((*x).try_into().unwrap(), (*y).try_into().unwrap());
|
||||||
|
let adjacent = self.get_adjacent(&coords);
|
||||||
|
adjacent < 4 && matches!(self.get(&coords), Space::Roll)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if accessible.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (x, y) in &accessible {
|
||||||
|
self.0[*x].0[*y] = Space::Empty;
|
||||||
|
}
|
||||||
|
total_removed += accessible.len();
|
||||||
|
}
|
||||||
|
total_removed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_adjacent(&self, coords: &Coords) -> usize {
|
||||||
|
let max_x = self.0.len() - 1;
|
||||||
|
let max_y = self.0[0].0.len() - 1;
|
||||||
|
|
||||||
|
(-1..=1)
|
||||||
|
.flat_map(|x| (-1..=1).map(move |y| (x, y)))
|
||||||
|
.filter(|(x, y)| {
|
||||||
|
!(*x == 0 && *y == 0)
|
||||||
|
&& !((coords.x == 0 && *x == -1)
|
||||||
|
|| (coords.y == 0 && *y == -1)
|
||||||
|
|| (usize::try_from(coords.x).unwrap() == max_x && *x == 1)
|
||||||
|
|| (usize::try_from(coords.y).unwrap() == max_y && *y == 1))
|
||||||
|
})
|
||||||
|
.filter(|(x, y)| {
|
||||||
|
let coord = coords.offset(&Coords::new(
|
||||||
|
(*x).try_into().unwrap(),
|
||||||
|
(*y).try_into().unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
|
matches!(self.get(&coord), Space::Roll)
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, coords: &Coords) -> Space {
|
||||||
|
self.0[usize::try_from(coords.x).unwrap()].0[usize::try_from(coords.y).unwrap()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let mut grid = Grid::from_str(input)?;
|
||||||
|
Ok(grid.find_accessible())
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "..@@.@@@@.
|
||||||
|
@@@.@.@.@@
|
||||||
|
@@@@@.@.@@
|
||||||
|
@.@@@@..@.
|
||||||
|
@@.@@@@.@@
|
||||||
|
.@@@@@@@.@
|
||||||
|
.@.@.@.@@@
|
||||||
|
@.@@@.@@@@
|
||||||
|
.@@@@@@@@.
|
||||||
|
@.@.@@@.@.
|
||||||
|
";
|
||||||
|
let result = 43;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-05/Cargo.toml
Normal file
27
2025/day-05/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-05"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-05-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-05/bench.txt
Normal file
4
2025/day-05/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_05_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 103.9 µs │ 151.8 µs │ 105.6 µs │ 107.3 µs │ 100 │ 100
|
||||||
|
╰─ part2 9.687 µs │ 14.12 µs │ 9.787 µs │ 9.895 µs │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-05/benches/benchmarks.rs
Normal file
15
2025/day-05/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_05::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-05/src/bin/part1.rs
Normal file
12
2025/day-05/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_05::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-05/src/bin/part2.rs
Normal file
12
2025/day-05/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_05::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-05/src/lib.rs
Normal file
2
2025/day-05/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
106
2025/day-05/src/part1.rs
Normal file
106
2025/day-05/src/part1.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct Id(usize);
|
||||||
|
|
||||||
|
impl From<usize> for Id {
|
||||||
|
fn from(value: usize) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Id {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let num = s.trim().parse::<usize>().map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Range {
|
||||||
|
start: Id,
|
||||||
|
end: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Range {
|
||||||
|
fn contains(&self, x: Id) -> bool {
|
||||||
|
x >= self.start && x <= self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Range {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (start_str, end_str) = s.trim().split_once('-').ok_or("`-` not found")?;
|
||||||
|
let start = start_str.parse::<Id>()?;
|
||||||
|
let end = end_str.parse::<Id>()?;
|
||||||
|
Ok(Self { start, end })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct DB {
|
||||||
|
ranges: Vec<Range>,
|
||||||
|
ids: Vec<Id>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DB {
|
||||||
|
fn contains(&self, x: Id) -> bool {
|
||||||
|
self.ranges.iter().any(|range| range.contains(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_fresh(&self) -> usize {
|
||||||
|
self.ids.iter().filter(|&&id| self.contains(id)).count()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DB {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (ranges_section, ids_section) = s
|
||||||
|
.split_once("\n\n")
|
||||||
|
.ok_or("No blank line separator found")?;
|
||||||
|
let ranges = ranges_section
|
||||||
|
.lines()
|
||||||
|
.map(Range::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let ids = ids_section
|
||||||
|
.lines()
|
||||||
|
.map(Id::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self { ranges, ids })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let db = DB::from_str(input).unwrap();
|
||||||
|
Ok(db.count_fresh())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "3-5
|
||||||
|
10-14
|
||||||
|
16-20
|
||||||
|
12-18
|
||||||
|
|
||||||
|
1
|
||||||
|
5
|
||||||
|
8
|
||||||
|
11
|
||||||
|
17
|
||||||
|
32
|
||||||
|
";
|
||||||
|
let result = 3;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
107
2025/day-05/src/part2.rs
Normal file
107
2025/day-05/src/part2.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct Id(usize);
|
||||||
|
|
||||||
|
impl From<usize> for Id {
|
||||||
|
fn from(value: usize) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Id {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let num = s.trim().parse::<usize>().map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Range {
|
||||||
|
start: Id,
|
||||||
|
end: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Range {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (start_str, end_str) = s.trim().split_once('-').ok_or("`-` not found")?;
|
||||||
|
let start = start_str.parse::<Id>()?;
|
||||||
|
let end = end_str.parse::<Id>()?;
|
||||||
|
Ok(Self { start, end })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct DB {
|
||||||
|
ranges: Vec<Range>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DB {
|
||||||
|
fn count_range_ids(&self) -> usize {
|
||||||
|
let mut sorted_ranges = self.ranges.clone();
|
||||||
|
sorted_ranges.sort_by_key(|r| r.start.0);
|
||||||
|
|
||||||
|
let (total, start, end) =
|
||||||
|
sorted_ranges
|
||||||
|
.into_iter()
|
||||||
|
.fold((0, None, None), |(total, s, e), range| match (s, e) {
|
||||||
|
(Some(s), Some(e)) if range.start.0 <= e + 1 => {
|
||||||
|
(total, Some(s), Some(range.end.0.max(e)))
|
||||||
|
}
|
||||||
|
(Some(s), Some(e)) => {
|
||||||
|
(total + (e - s + 1), Some(range.start.0), Some(range.end.0))
|
||||||
|
}
|
||||||
|
_ => (total, Some(range.start.0), Some(range.end.0)),
|
||||||
|
});
|
||||||
|
|
||||||
|
total + start.zip(end).map_or(0, |(s, e)| e - s + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DB {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (ranges_section, _) = s
|
||||||
|
.split_once("\n\n")
|
||||||
|
.ok_or("No blank line separator found")?;
|
||||||
|
let ranges = ranges_section
|
||||||
|
.lines()
|
||||||
|
.map(Range::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self { ranges })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let db = DB::from_str(input).unwrap();
|
||||||
|
Ok(db.count_range_ids())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "3-5
|
||||||
|
10-14
|
||||||
|
16-20
|
||||||
|
12-18
|
||||||
|
|
||||||
|
1
|
||||||
|
5
|
||||||
|
8
|
||||||
|
11
|
||||||
|
17
|
||||||
|
32
|
||||||
|
";
|
||||||
|
let result = 14;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-06/Cargo.toml
Normal file
27
2025/day-06/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-06"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-06-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
3
2025/day-06/bench.txt
Normal file
3
2025/day-06/bench.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
day_06_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 69.96 µs │ 141.7 µs │ 70.27 µs │ 73 µs │ 100 │ 100
|
||||||
|
╰─ part2
|
||||||
15
2025/day-06/benches/benchmarks.rs
Normal file
15
2025/day-06/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_06::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-06/src/bin/part1.rs
Normal file
12
2025/day-06/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_06::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-06/src/bin/part2.rs
Normal file
12
2025/day-06/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_06::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-06/src/lib.rs
Normal file
2
2025/day-06/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
102
2025/day-06/src/part1.rs
Normal file
102
2025/day-06/src/part1.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use miette::miette;
|
||||||
|
use std::{fmt::Display, str::FromStr, vec};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Row<T>(Vec<T>);
|
||||||
|
|
||||||
|
impl<T> FromStr for Row<T>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
T::Err: Display,
|
||||||
|
{
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let row = s
|
||||||
|
.split_whitespace()
|
||||||
|
.map(str::parse::<T>)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(row))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Grid<T>(Vec<T>);
|
||||||
|
|
||||||
|
impl<T: Clone> Grid<Row<T>> {
|
||||||
|
fn to_transposed(&self) -> Self {
|
||||||
|
if self.0.is_empty() {
|
||||||
|
return Self(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_cols = self.0[0].0.len();
|
||||||
|
let mut transposed = vec![Vec::new(); num_cols];
|
||||||
|
|
||||||
|
for row in &self.0 {
|
||||||
|
for (col_idx, val) in row.0.iter().enumerate() {
|
||||||
|
if col_idx < transposed.len() {
|
||||||
|
transposed[col_idx].push(val.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self(transposed.into_iter().map(Row).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FromStr for Grid<T>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
T::Err: Display,
|
||||||
|
{
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let rows = s
|
||||||
|
.lines()
|
||||||
|
.map(T::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self(rows))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let (rest, last_line) = match input.trim_end().rsplit_once('\n') {
|
||||||
|
Some((r, l)) => (r, l),
|
||||||
|
None => ("", ""),
|
||||||
|
};
|
||||||
|
let digits = rest
|
||||||
|
.parse::<Grid<Row<usize>>>()
|
||||||
|
.map_err(|e| miette!("{e}"))?;
|
||||||
|
let operators = last_line.parse::<Row<char>>().map_err(|e| miette!("{e}"))?;
|
||||||
|
let result = operators
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.zip(digits.to_transposed().0)
|
||||||
|
.map(|(operator, row)| match operator {
|
||||||
|
'+' => row.0.iter().sum(),
|
||||||
|
'*' => row.0.iter().product::<usize>(),
|
||||||
|
op => panic!("Unknown operator: {op}"),
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "123 328 51 64
|
||||||
|
45 64 387 23
|
||||||
|
6 98 215 314
|
||||||
|
* + * + ";
|
||||||
|
let result = 4_277_556;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
23
2025/day-06/src/part2.rs
Normal file
23
2025/day-06/src/part2.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use miette::Result;
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> Result<usize> {
|
||||||
|
todo!("day xx - part 2");
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> Result<()> {
|
||||||
|
let input = "";
|
||||||
|
todo!("haven't built test yet");
|
||||||
|
let result = 0;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-07/Cargo.toml
Normal file
27
2025/day-07/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-07"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-07-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-07/bench.txt
Normal file
4
2025/day-07/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_07_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 366.1 µs │ 407.2 µs │ 369.8 µs │ 370.9 µs │ 100 │ 100
|
||||||
|
╰─ part2 273.4 µs │ 291.4 µs │ 277.4 µs │ 278.5 µs │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-07/benches/benchmarks.rs
Normal file
15
2025/day-07/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_07::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-07/src/bin/part1.rs
Normal file
12
2025/day-07/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_07::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-07/src/bin/part2.rs
Normal file
12
2025/day-07/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_07::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-07/src/lib.rs
Normal file
2
2025/day-07/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
92
2025/day-07/src/part1.rs
Normal file
92
2025/day-07/src/part1.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
type Column = usize;
|
||||||
|
type Row = usize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
struct Splitter {
|
||||||
|
column: Column,
|
||||||
|
row: Row,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SimulationState {
|
||||||
|
active_positions: HashSet<Column>,
|
||||||
|
splitters: HashSet<Splitter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimulationState {
|
||||||
|
fn new(start_column: Column) -> Self {
|
||||||
|
let mut active_positions = HashSet::new();
|
||||||
|
active_positions.insert(start_column);
|
||||||
|
Self {
|
||||||
|
active_positions,
|
||||||
|
splitters: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_row(&mut self, row: Row, line: &str) {
|
||||||
|
let mut next_positions = HashSet::new();
|
||||||
|
|
||||||
|
for col in &self.active_positions {
|
||||||
|
if line.as_bytes()[*col] == b'^' {
|
||||||
|
info!(?col, "split at");
|
||||||
|
next_positions.insert(col - 1);
|
||||||
|
next_positions.insert(col + 1);
|
||||||
|
next_positions.insert(col + 1);
|
||||||
|
self.splitters.insert(Splitter { column: *col, row });
|
||||||
|
} else {
|
||||||
|
next_positions.insert(*col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_positions = next_positions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let mut lines = input.lines().enumerate();
|
||||||
|
let (_, first_line) = lines.next().unwrap();
|
||||||
|
let start_column = first_line.chars().position(|ch| ch == 'S').unwrap();
|
||||||
|
|
||||||
|
let mut state = SimulationState::new(start_column);
|
||||||
|
|
||||||
|
for (row, line) in lines {
|
||||||
|
state.process_row(row, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(state.splitters.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = ".......S.......
|
||||||
|
...............
|
||||||
|
.......^.......
|
||||||
|
...............
|
||||||
|
......^.^......
|
||||||
|
...............
|
||||||
|
.....^.^.^.....
|
||||||
|
...............
|
||||||
|
....^.^...^....
|
||||||
|
...............
|
||||||
|
...^.^...^.^...
|
||||||
|
...............
|
||||||
|
..^...^.....^..
|
||||||
|
...............
|
||||||
|
.^.^.^.^.^...^.
|
||||||
|
...............
|
||||||
|
";
|
||||||
|
let result = 21;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
82
2025/day-07/src/part2.rs
Normal file
82
2025/day-07/src/part2.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
type Column = usize;
|
||||||
|
type PathCount = usize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct PathCounts {
|
||||||
|
positions: HashMap<Column, PathCount>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathCounts {
|
||||||
|
fn new(start_column: Column) -> Self {
|
||||||
|
let mut positions = HashMap::new();
|
||||||
|
positions.insert(start_column, 1);
|
||||||
|
Self { positions }
|
||||||
|
}
|
||||||
|
fn apply_row(&self, line: &str) -> Self {
|
||||||
|
let mut new_positions = HashMap::new();
|
||||||
|
|
||||||
|
for (&column, &count) in &self.positions {
|
||||||
|
if line.as_bytes()[column] == b'^' {
|
||||||
|
info!(column, "split at");
|
||||||
|
*new_positions.entry(column - 1).or_insert(0) += count;
|
||||||
|
*new_positions.entry(column + 1).or_insert(0) += count;
|
||||||
|
} else {
|
||||||
|
*new_positions.entry(column).or_insert(0) += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
positions: new_positions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total_paths(&self) -> usize {
|
||||||
|
self.positions.values().sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let mut lines = input.lines().enumerate();
|
||||||
|
let (_, first_line) = lines.next().unwrap();
|
||||||
|
let start_column = first_line.chars().position(|ch| ch == 'S').unwrap();
|
||||||
|
|
||||||
|
let final_state = lines.fold(PathCounts::new(start_column), |state, (_, line)| {
|
||||||
|
state.apply_row(line)
|
||||||
|
});
|
||||||
|
Ok(final_state.total_paths())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = ".......S.......
|
||||||
|
...............
|
||||||
|
.......^.......
|
||||||
|
...............
|
||||||
|
......^.^......
|
||||||
|
...............
|
||||||
|
.....^.^.^.....
|
||||||
|
...............
|
||||||
|
....^.^...^....
|
||||||
|
...............
|
||||||
|
...^.^...^.^...
|
||||||
|
...............
|
||||||
|
..^...^.....^..
|
||||||
|
...............
|
||||||
|
.^.^.^.^.^...^.
|
||||||
|
...............
|
||||||
|
";
|
||||||
|
let result = 40;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
28
2025/day-08/Cargo.toml
Normal file
28
2025/day-08/Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-08"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
glam = { workspace = true, features = ["mint"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-08-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-08/bench.txt
Normal file
4
2025/day-08/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_08_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 24.77 ms │ 29.97 ms │ 25.17 ms │ 25.63 ms │ 100 │ 100
|
||||||
|
╰─ part2 24.89 ms │ 33.97 ms │ 25.34 ms │ 25.74 ms │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-08/benches/benchmarks.rs
Normal file
15
2025/day-08/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_08::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt")), 1000).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-08/src/bin/part1.rs
Normal file
12
2025/day-08/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_08::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file, 1000).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-08/src/bin/part2.rs
Normal file
12
2025/day-08/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_08::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-08/src/lib.rs
Normal file
2
2025/day-08/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
142
2025/day-08/src/part1.rs
Normal file
142
2025/day-08/src/part1.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use glam::Vec3;
|
||||||
|
use miette::miette;
|
||||||
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
|
fn vec3_from_str(s: &str) -> Result<Vec3, String> {
|
||||||
|
let coords = s
|
||||||
|
.trim()
|
||||||
|
.split(',')
|
||||||
|
.map(|x| {
|
||||||
|
x.parse::<f32>()
|
||||||
|
.map_err(|_| "Invalid coordinate value".to_string())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
if coords.len() == 3 {
|
||||||
|
let vec = Vec3::new(coords[0], coords[1], coords[2]);
|
||||||
|
return Ok(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("Expected exactly 3 coordinates".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct UnionFind {
|
||||||
|
parent: Vec<usize>,
|
||||||
|
size: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnionFind {
|
||||||
|
fn new(n: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
parent: (0..n).collect(),
|
||||||
|
size: vec![1; n],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find(&mut self, x: usize) -> usize {
|
||||||
|
if self.parent[x] != x {
|
||||||
|
self.parent[x] = self.find(self.parent[x]);
|
||||||
|
}
|
||||||
|
self.parent[x]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn union(&mut self, mut x: usize, mut y: usize) {
|
||||||
|
x = self.find(x);
|
||||||
|
y = self.find(y);
|
||||||
|
if x == y {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.size[x] < self.size[y] {
|
||||||
|
(x, y) = (y, x);
|
||||||
|
}
|
||||||
|
self.parent[y] = x;
|
||||||
|
self.size[x] += self.size[y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Coordinates(Vec<Vec3>);
|
||||||
|
|
||||||
|
impl Coordinates {
|
||||||
|
fn solve(&self, size: usize) -> usize {
|
||||||
|
let n = self.0.len();
|
||||||
|
|
||||||
|
let mut pairs = Vec::new();
|
||||||
|
for i in 0..n {
|
||||||
|
for j in (i + 1)..n {
|
||||||
|
let dist = self.0[i].distance(self.0[j]);
|
||||||
|
pairs.push((dist, i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||||
|
|
||||||
|
let mut uf = UnionFind::new(n);
|
||||||
|
for (_, i, j) in pairs.iter().take(size) {
|
||||||
|
if uf.find(*i) != uf.find(*j) {
|
||||||
|
uf.union(*i, *j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut roots = HashSet::new();
|
||||||
|
for i in 0..n {
|
||||||
|
roots.insert(uf.find(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sizes = roots.iter().map(|&root| uf.size[root]).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
sizes.sort_by(|a, b| b.cmp(a));
|
||||||
|
sizes[0] * sizes[1] * sizes[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Coordinates {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let coords = s
|
||||||
|
.lines()
|
||||||
|
.map(vec3_from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(coords))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str, size: usize) -> miette::Result<usize> {
|
||||||
|
let coords = Coordinates::from_str(input).map_err(|e| miette!("{e}"))?;
|
||||||
|
Ok(coords.solve(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "162,817,812
|
||||||
|
57,618,57
|
||||||
|
906,360,560
|
||||||
|
592,479,940
|
||||||
|
352,342,300
|
||||||
|
466,668,158
|
||||||
|
542,29,236
|
||||||
|
431,825,988
|
||||||
|
739,650,466
|
||||||
|
52,470,668
|
||||||
|
216,146,977
|
||||||
|
819,987,18
|
||||||
|
117,168,530
|
||||||
|
805,96,715
|
||||||
|
346,949,466
|
||||||
|
970,615,88
|
||||||
|
941,993,340
|
||||||
|
862,61,35
|
||||||
|
984,92,344
|
||||||
|
425,690,689";
|
||||||
|
let result = 40;
|
||||||
|
assert_eq!(process(input, 10)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
142
2025/day-08/src/part2.rs
Normal file
142
2025/day-08/src/part2.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use glam::Vec3;
|
||||||
|
use miette::miette;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
fn vec3_from_str(s: &str) -> Result<Vec3, String> {
|
||||||
|
let coords = s
|
||||||
|
.trim()
|
||||||
|
.split(',')
|
||||||
|
.map(|x| {
|
||||||
|
x.parse::<f32>()
|
||||||
|
.map_err(|_| "Invalid coordinate value".to_string())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
if coords.len() == 3 {
|
||||||
|
let vec = Vec3::new(coords[0], coords[1], coords[2]);
|
||||||
|
return Ok(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("Expected exactly 3 coordinates".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct UnionFind {
|
||||||
|
parent: Vec<usize>,
|
||||||
|
size: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnionFind {
|
||||||
|
fn new(n: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
parent: (0..n).collect(),
|
||||||
|
size: vec![1; n],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find(&mut self, x: usize) -> usize {
|
||||||
|
if self.parent[x] != x {
|
||||||
|
self.parent[x] = self.find(self.parent[x]);
|
||||||
|
}
|
||||||
|
self.parent[x]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn union(&mut self, mut x: usize, mut y: usize) {
|
||||||
|
x = self.find(x);
|
||||||
|
y = self.find(y);
|
||||||
|
if x == y {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.size[x] < self.size[y] {
|
||||||
|
(x, y) = (y, x);
|
||||||
|
}
|
||||||
|
self.parent[y] = x;
|
||||||
|
self.size[x] += self.size[y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Coordinates(Vec<Vec3>);
|
||||||
|
|
||||||
|
impl Coordinates {
|
||||||
|
fn solve(&self) -> usize {
|
||||||
|
let n = self.0.len();
|
||||||
|
|
||||||
|
let mut pairs = Vec::new();
|
||||||
|
for i in 0..n {
|
||||||
|
for j in (i + 1)..n {
|
||||||
|
let dist = self.0[i].distance(self.0[j]);
|
||||||
|
pairs.push((dist, i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||||
|
|
||||||
|
let mut uf = UnionFind::new(n);
|
||||||
|
let mut last_pair = (0, 0);
|
||||||
|
let mut num_circuits = n;
|
||||||
|
|
||||||
|
for (_, i, j) in &pairs {
|
||||||
|
if uf.find(*i) != uf.find(*j) {
|
||||||
|
uf.union(*i, *j);
|
||||||
|
last_pair = (*i, *j);
|
||||||
|
num_circuits -= 1;
|
||||||
|
|
||||||
|
if num_circuits == 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(self.0[last_pair.0].x as usize) * (self.0[last_pair.1].x as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Coordinates {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let coords = s
|
||||||
|
.lines()
|
||||||
|
.map(vec3_from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(coords))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let coords = Coordinates::from_str(input).map_err(|e| miette!("{e}"))?;
|
||||||
|
Ok(coords.solve())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "162,817,812
|
||||||
|
57,618,57
|
||||||
|
906,360,560
|
||||||
|
592,479,940
|
||||||
|
352,342,300
|
||||||
|
466,668,158
|
||||||
|
542,29,236
|
||||||
|
431,825,988
|
||||||
|
739,650,466
|
||||||
|
52,470,668
|
||||||
|
216,146,977
|
||||||
|
819,987,18
|
||||||
|
117,168,530
|
||||||
|
805,96,715
|
||||||
|
346,949,466
|
||||||
|
970,615,88
|
||||||
|
941,993,340
|
||||||
|
862,61,35
|
||||||
|
984,92,344
|
||||||
|
425,690,689";
|
||||||
|
let result = 25272;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
28
2025/day-09/Cargo.toml
Normal file
28
2025/day-09/Cargo.toml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-09"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
glam.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-09-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
3
2025/day-09/bench.txt
Normal file
3
2025/day-09/bench.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
day_09_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 267.7 µs │ 279.6 µs │ 268.8 µs │ 269.8 µs │ 100 │ 100
|
||||||
|
╰─ part2
|
||||||
15
2025/day-09/benches/benchmarks.rs
Normal file
15
2025/day-09/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_09::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-09/src/bin/part1.rs
Normal file
12
2025/day-09/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_09::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-09/src/bin/part2.rs
Normal file
12
2025/day-09/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_09::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-09/src/lib.rs
Normal file
2
2025/day-09/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
93
2025/day-09/src/part1.rs
Normal file
93
2025/day-09/src/part1.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use glam::{USizeVec2, usize};
|
||||||
|
use miette::miette;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Tile(USizeVec2);
|
||||||
|
|
||||||
|
impl Tile {
|
||||||
|
const fn x(&self) -> usize {
|
||||||
|
self.0.x
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn y(&self) -> usize {
|
||||||
|
self.0.y
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn area(&self, other: Self) -> usize {
|
||||||
|
(self.x().abs_diff(other.x()) + 1) * (self.y().abs_diff(other.y()) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Tile {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let str = s.trim().split(',').collect::<Vec<_>>();
|
||||||
|
if str.len() < 2 {
|
||||||
|
return Err("Missing coords".to_string());
|
||||||
|
}
|
||||||
|
Ok(Self(USizeVec2 {
|
||||||
|
x: str_to_usize(str[0])?,
|
||||||
|
y: str_to_usize(str[1])?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_to_usize(s: &str) -> Result<usize, String> {
|
||||||
|
s.parse::<usize>().map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Grid(Vec<Tile>);
|
||||||
|
|
||||||
|
impl FromStr for Grid {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let grid = s
|
||||||
|
.trim()
|
||||||
|
.lines()
|
||||||
|
.map(Tile::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(grid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
fn get_max_area(&self) -> usize {
|
||||||
|
self.area_list().max().unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn area_list(&self) -> impl Iterator<Item = usize> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.flat_map(|&a| self.0.iter().map(move |&b| a.area(b)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let grid = Grid::from_str(input).map_err(|e| miette!("{e}"))?;
|
||||||
|
Ok(grid.get_max_area())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "7,1
|
||||||
|
11,1
|
||||||
|
11,7
|
||||||
|
9,7
|
||||||
|
9,5
|
||||||
|
2,5
|
||||||
|
2,3
|
||||||
|
7,3";
|
||||||
|
let result = 50;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
23
2025/day-09/src/part2.rs
Normal file
23
2025/day-09/src/part2.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use miette::Result;
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> Result<usize> {
|
||||||
|
todo!("day xx - part 2");
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> Result<()> {
|
||||||
|
let input = "";
|
||||||
|
todo!("haven't built test yet");
|
||||||
|
let result = 0;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-10/Cargo.toml
Normal file
27
2025/day-10/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-10"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-10-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
3
2025/day-10/bench.txt
Normal file
3
2025/day-10/bench.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
day_10_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 7.256 ms │ 8.22 ms │ 7.328 ms │ 7.348 ms │ 100 │ 100
|
||||||
|
╰─ part2
|
||||||
15
2025/day-10/benches/benchmarks.rs
Normal file
15
2025/day-10/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_10::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-10/src/bin/part1.rs
Normal file
12
2025/day-10/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_10::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-10/src/bin/part2.rs
Normal file
12
2025/day-10/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_10::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-10/src/lib.rs
Normal file
2
2025/day-10/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
207
2025/day-10/src/part1.rs
Normal file
207
2025/day-10/src/part1.rs
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
use miette::miette;
|
||||||
|
use std::{collections::HashSet, slice::Iter, str::FromStr, vec};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum Light {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for Light {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(ch: char) -> Result<Self, Self::Error> {
|
||||||
|
match ch {
|
||||||
|
'#' => Ok(Self::On),
|
||||||
|
'.' => Ok(Self::Off),
|
||||||
|
_ => Err("Unknown Light status".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Light {
|
||||||
|
const fn toggle(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::On => Self::Off,
|
||||||
|
Self::Off => Self::On,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [.##.]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
struct IndicatorLight(Vec<Light>);
|
||||||
|
|
||||||
|
impl FromStr for IndicatorLight {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
let digits_str = trimmed
|
||||||
|
.strip_prefix('[')
|
||||||
|
.and_then(|s| s.strip_suffix(']'))
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
let lights = digits_str
|
||||||
|
.chars()
|
||||||
|
.map(Light::try_from)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(lights))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndicatorLight {
|
||||||
|
fn toggle(&mut self, button: &Button) {
|
||||||
|
for &btn in button.iter() {
|
||||||
|
let light = self.0.get_mut(btn).expect("light exsists");
|
||||||
|
*light = light.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Self(vec![Light::Off; capacity])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (3) (1,3) (2) (2,3) (0,2) (0,1)
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Button(Vec<usize>);
|
||||||
|
|
||||||
|
impl FromStr for Button {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
let digits_str = trimmed
|
||||||
|
.strip_prefix('(')
|
||||||
|
.and_then(|s| s.strip_suffix(')'))
|
||||||
|
.unwrap_or(trimmed);
|
||||||
|
|
||||||
|
let button = digits_str
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.parse::<usize>().map_err(|e| e.to_string()))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(button))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Button {
|
||||||
|
fn iter(&self) -> Iter<'_, usize> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {3,5,4,7}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Joltage(Vec<usize>);
|
||||||
|
|
||||||
|
impl FromStr for Joltage {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
let digits_str = trimmed
|
||||||
|
.strip_prefix('{')
|
||||||
|
.and_then(|s| s.strip_suffix('}'))
|
||||||
|
.unwrap_or(trimmed);
|
||||||
|
|
||||||
|
let joltage = digits_str
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.parse::<usize>().map_err(|e| e.to_string()))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(joltage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Machine {
|
||||||
|
final_state: IndicatorLight, // [.##.]
|
||||||
|
current_state: IndicatorLight,
|
||||||
|
buttons: Vec<Button>, // (3) (1,3) (2) (2,3) (0,2) (0,1)
|
||||||
|
_joltage: Joltage, // {3,5,4,7}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Machine {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
|
||||||
|
let bracket_start = trimmed.find('[').ok_or("Missing '['")?;
|
||||||
|
let bracket_end = trimmed.find(']').ok_or("Missing ']'")?;
|
||||||
|
let final_state = trimmed[bracket_start..=bracket_end].parse::<IndicatorLight>()?;
|
||||||
|
let state_len = final_state.len();
|
||||||
|
|
||||||
|
let brace_start = trimmed.find('{').ok_or("Missing '{'")?;
|
||||||
|
let brace_end = trimmed.find('}').ok_or("Missing '}'")?;
|
||||||
|
let joltage = trimmed[brace_start..=brace_end].parse()?;
|
||||||
|
|
||||||
|
let buttons_str = trimmed[bracket_end + 1..brace_start].trim();
|
||||||
|
let buttons = buttons_str
|
||||||
|
.split_whitespace()
|
||||||
|
.map(Button::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
final_state,
|
||||||
|
current_state: IndicatorLight::with_capacity(state_len),
|
||||||
|
buttons,
|
||||||
|
_joltage: joltage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let machines = input
|
||||||
|
.lines()
|
||||||
|
.map(Machine::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|e| miette!("{e}"))?;
|
||||||
|
|
||||||
|
let result = machines
|
||||||
|
.iter()
|
||||||
|
.map(|machine| {
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
set.insert(machine.current_state.clone());
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
set = set
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|state| {
|
||||||
|
machine.buttons.iter().map(move |btn| {
|
||||||
|
let mut new_state = state.clone();
|
||||||
|
new_state.toggle(btn);
|
||||||
|
new_state
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
i += 1;
|
||||||
|
if set.contains(&machine.final_state) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
|
||||||
|
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
|
||||||
|
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}";
|
||||||
|
|
||||||
|
let result = 7;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
23
2025/day-10/src/part2.rs
Normal file
23
2025/day-10/src/part2.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use miette::miette;
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
todo!("day xx - part 2");
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "";
|
||||||
|
todo!("haven't built test yet");
|
||||||
|
let result = 0;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
27
2025/day-11/Cargo.toml
Normal file
27
2025/day-11/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "day-11"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan.workspace = true
|
||||||
|
rstest.workspace = true
|
||||||
|
test-log.workspace = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "day-11-bench"
|
||||||
|
path = "benches/benchmarks.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
4
2025/day-11/bench.txt
Normal file
4
2025/day-11/bench.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
day_11_bench fastest │ slowest │ median │ mean │ samples │ iters
|
||||||
|
├─ part1 129.6 µs │ 171.1 µs │ 131.1 µs │ 133.9 µs │ 100 │ 100
|
||||||
|
╰─ part2 320.8 µs │ 370.5 µs │ 333.9 µs │ 335.6 µs │ 100 │ 100
|
||||||
|
|
||||||
15
2025/day-11/benches/benchmarks.rs
Normal file
15
2025/day-11/benches/benchmarks.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use day_11::{part1, part2};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part1() {
|
||||||
|
part1::process(divan::black_box(include_str!("../input1.txt"))).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn part2() {
|
||||||
|
part2::process(divan::black_box(include_str!("../input2.txt"))).unwrap();
|
||||||
|
}
|
||||||
12
2025/day-11/src/bin/part1.rs
Normal file
12
2025/day-11/src/bin/part1.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_11::part1::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input1.txt");
|
||||||
|
let result = process(file).context("process part 1")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
12
2025/day-11/src/bin/part2.rs
Normal file
12
2025/day-11/src/bin/part2.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use day_11::part2::process;
|
||||||
|
use miette::{Context, Result};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let file = include_str!("../../input2.txt");
|
||||||
|
let result = process(file).context("process part 2")?;
|
||||||
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
2025/day-11/src/lib.rs
Normal file
2
2025/day-11/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
113
2025/day-11/src/part1.rs
Normal file
113
2025/day-11/src/part1.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use miette::miette;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct Name([char; 3]);
|
||||||
|
|
||||||
|
impl Name {
|
||||||
|
const YOU: Self = Self(['y', 'o', 'u']);
|
||||||
|
const OUT: Self = Self(['o', 'u', 't']);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Name {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let chars = s.trim().chars().collect_vec();
|
||||||
|
if chars.len() != 3 {
|
||||||
|
return Err(format!(
|
||||||
|
"Name must be exactly 3 characters, got {}",
|
||||||
|
chars.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(Self([chars[0], chars[1], chars[2]]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Device {
|
||||||
|
input: Name,
|
||||||
|
outputs: Vec<Name>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Device {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
let (input_str, output_str) = trimmed.split_once(':').ok_or("Missing `:`")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
input: Name::from_str(input_str)?,
|
||||||
|
outputs: output_str
|
||||||
|
.split_whitespace()
|
||||||
|
.map(Name::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Rack(Vec<Device>);
|
||||||
|
|
||||||
|
impl FromStr for Rack {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let rack = s
|
||||||
|
.lines()
|
||||||
|
.map(Device::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(rack))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rack {
|
||||||
|
fn solve(&self) -> usize {
|
||||||
|
dfs(&Name::YOU, &self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs(current: &Name, devices: &[Device]) -> usize {
|
||||||
|
if current == &Name::OUT {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
devices
|
||||||
|
.iter()
|
||||||
|
.find(|d| d.input == *current)
|
||||||
|
.map_or(0, |device| {
|
||||||
|
device
|
||||||
|
.outputs
|
||||||
|
.iter()
|
||||||
|
.map(|output| dfs(output, devices))
|
||||||
|
.sum()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let rack = Rack::from_str(input).map_err(|e| miette!("{e}"))?;
|
||||||
|
Ok(rack.solve())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "aaa: you hhh
|
||||||
|
you: bbb ccc
|
||||||
|
bbb: ddd eee
|
||||||
|
ccc: ddd eee fff
|
||||||
|
ddd: ggg
|
||||||
|
eee: out
|
||||||
|
fff: out
|
||||||
|
ggg: out
|
||||||
|
hhh: ccc fff iii
|
||||||
|
iii: out";
|
||||||
|
let result = 5;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
159
2025/day-11/src/part2.rs
Normal file
159
2025/day-11/src/part2.rs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use miette::miette;
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
struct Name([char; 3]);
|
||||||
|
|
||||||
|
impl Name {
|
||||||
|
const OUT: Self = Self(['o', 'u', 't']);
|
||||||
|
const DAC: Self = Self(['d', 'a', 'c']);
|
||||||
|
const FFT: Self = Self(['f', 'f', 't']);
|
||||||
|
const SVR: Self = Self(['s', 'v', 'r']);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Name {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let chars = s.trim().chars().collect_vec();
|
||||||
|
if chars.len() != 3 {
|
||||||
|
return Err(format!(
|
||||||
|
"Name must be exactly 3 characters, got {}",
|
||||||
|
chars.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(Self([chars[0], chars[1], chars[2]]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Device {
|
||||||
|
input: Name,
|
||||||
|
outputs: Vec<Name>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Device {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
let (input_str, output_str) = trimmed.split_once(':').ok_or("Missing `:`")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
input: Name::from_str(input_str)?,
|
||||||
|
outputs: output_str
|
||||||
|
.split_whitespace()
|
||||||
|
.map(Name::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Rack(Vec<Device>);
|
||||||
|
|
||||||
|
impl FromStr for Rack {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let rack = s
|
||||||
|
.lines()
|
||||||
|
.map(Device::from_str)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(rack))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rack {
|
||||||
|
fn solve(&self) -> usize {
|
||||||
|
let mut memo = HashMap::new();
|
||||||
|
let mut computing = HashSet::new();
|
||||||
|
dfs(&Name::SVR, &self.0, false, false, &mut memo, &mut computing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs(
|
||||||
|
current: &Name,
|
||||||
|
devices: &[Device],
|
||||||
|
visited_dac: bool,
|
||||||
|
visited_fft: bool,
|
||||||
|
memo: &mut HashMap<(Name, bool, bool), usize>,
|
||||||
|
computing: &mut HashSet<(Name, bool, bool)>,
|
||||||
|
) -> usize {
|
||||||
|
let state = (*current, visited_dac, visited_fft);
|
||||||
|
|
||||||
|
if let Some(&result) = memo.get(&state) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if computing.contains(&state) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if current == &Name::OUT {
|
||||||
|
return usize::from(visited_dac && visited_fft);
|
||||||
|
}
|
||||||
|
|
||||||
|
computing.insert(state);
|
||||||
|
|
||||||
|
let new_visited_dac = visited_dac || (current == &Name::DAC);
|
||||||
|
let new_visited_fft = visited_fft || (current == &Name::FFT);
|
||||||
|
|
||||||
|
let result = devices
|
||||||
|
.iter()
|
||||||
|
.find(|d| d.input == *current)
|
||||||
|
.map_or(0, |device| {
|
||||||
|
device
|
||||||
|
.outputs
|
||||||
|
.iter()
|
||||||
|
.map(|output| {
|
||||||
|
dfs(
|
||||||
|
output,
|
||||||
|
devices,
|
||||||
|
new_visited_dac,
|
||||||
|
new_visited_fft,
|
||||||
|
memo,
|
||||||
|
computing,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
});
|
||||||
|
|
||||||
|
computing.remove(&state);
|
||||||
|
memo.insert(state, result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn process(input: &str) -> miette::Result<usize> {
|
||||||
|
let rack = Rack::from_str(input).map_err(|e| miette!("{e}"))?;
|
||||||
|
Ok(rack.solve())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process() -> miette::Result<()> {
|
||||||
|
let input = "svr: aaa bbb
|
||||||
|
aaa: fft
|
||||||
|
fft: ccc
|
||||||
|
bbb: tty
|
||||||
|
tty: ccc
|
||||||
|
ccc: ddd eee
|
||||||
|
ddd: hub
|
||||||
|
hub: fff
|
||||||
|
eee: dac
|
||||||
|
dac: fff
|
||||||
|
fff: ggg hhh
|
||||||
|
ggg: out
|
||||||
|
hhh: out";
|
||||||
|
let result = 2;
|
||||||
|
assert_eq!(process(input)?, result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user