mirror of
https://github.com/kristoferssolo/Advent-of-Code.git
synced 2025-12-31 05:32:31 +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