tangled
alpha
login
or
join now
jonas.tngl.sh
/
advent-of-code
0
fork
atom
Advent of Code solutions in Rust
0
fork
atom
overview
issues
pulls
pipelines
feat: complete AoC 2025 ⭐️
jonas.tngl.sh
3 months ago
21cf9cc8
c5127164
1/1
rust.yml
success
3m 38s
+125
-2
4 changed files
expand all
collapse all
unified
split
Cargo.lock
aoc_2025
Cargo.toml
src
day12.rs
main.rs
+1
Cargo.lock
···
148
148
"anyhow",
149
149
"aoc_companion",
150
150
"aoc_utils",
151
151
+
"fxhash",
151
152
"itertools",
152
153
"ndarray",
153
154
"num-traits",
+1
aoc_2025/Cargo.toml
···
15
15
rayon = { workspace = true }
16
16
thiserror = { workspace = true }
17
17
tokio = { workspace = true }
18
18
+
fxhash = "0.2.1"
+121
aoc_2025/src/day12.rs
···
1
1
+
use std::{collections::HashSet, num::ParseIntError};
2
2
+
3
3
+
use anyhow::{Context, anyhow, bail};
4
4
+
use aoc_companion::prelude::*;
5
5
+
use itertools::Itertools as _;
6
6
+
7
7
+
pub(crate) struct Door {
8
8
+
problems: Vec<Problem>,
9
9
+
}
10
10
+
11
11
+
impl<'input> Solution<'input> for Door {
12
12
+
fn parse(input: &'input str) -> Result<Self> {
13
13
+
let Some((shapes, problems)) = input.rsplit_once("\n\n") else {
14
14
+
bail!("could not find empty line delimiting shapes and problems");
15
15
+
};
16
16
+
let shapes_hash = fxhash::hash(shapes);
17
17
+
if shapes_hash != 0x2050bd894f01430f {
18
18
+
bail!("solution only works for my input; got different hash: {shapes_hash:x}");
19
19
+
}
20
20
+
problems
21
21
+
.lines()
22
22
+
.map(str::parse)
23
23
+
.try_collect()
24
24
+
.map(|problems| Door { problems })
25
25
+
}
26
26
+
27
27
+
fn part1(&self) -> Result<usize> {
28
28
+
let (conclusive, inconclusive): (Vec<bool>, HashSet<&Problem>) = self
29
29
+
.problems
30
30
+
.iter()
31
31
+
.map(|problem| {
32
32
+
rule_out_due_to_insufficient_area(problem)
33
33
+
.or_else(|| verify_with_trivial_packing(problem))
34
34
+
.ok_or(problem)
35
35
+
})
36
36
+
.partition_result();
37
37
+
38
38
+
if !inconclusive.is_empty() {
39
39
+
bail!(
40
40
+
"{}/{} problems were inconclusive, e.g.: {}",
41
41
+
inconclusive.len(),
42
42
+
self.problems.len(),
43
43
+
inconclusive.iter().next().unwrap()
44
44
+
);
45
45
+
}
46
46
+
47
47
+
Ok(conclusive.into_iter().filter(|&b| b).count())
48
48
+
}
49
49
+
}
50
50
+
51
51
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52
52
+
struct Problem {
53
53
+
dimensions: [usize; 2],
54
54
+
presents: [usize; 6],
55
55
+
}
56
56
+
57
57
+
impl std::fmt::Display for Problem {
58
58
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59
59
+
write!(
60
60
+
f,
61
61
+
"{}: {}",
62
62
+
self.dimensions.iter().join("x"),
63
63
+
self.presents.iter().join(" ")
64
64
+
)
65
65
+
}
66
66
+
}
67
67
+
68
68
+
impl std::str::FromStr for Problem {
69
69
+
type Err = anyhow::Error;
70
70
+
71
71
+
fn from_str(s: &str) -> Result<Self> {
72
72
+
let Some((dimensions, shapes)) = s.split_once(':') else {
73
73
+
bail!("could not find colon delimiting problem dimensions and required shapes");
74
74
+
};
75
75
+
let dimensions = dimensions
76
76
+
.split_once('x')
77
77
+
.ok_or_else(|| anyhow!("missing 'x' in dimensions spec {dimensions:?}"))
78
78
+
.and_then(|(x, y)| {
79
79
+
Ok::<_, ParseIntError>([x.parse()?, y.parse()?])
80
80
+
.with_context(|| anyhow!("failed to parse region dimenions {dimensions:?}"))
81
81
+
})?;
82
82
+
// TODO: add try_from_iter_exact
83
83
+
let presents = aoc_utils::array::try_from_iter(shapes.split_whitespace().map(|s| {
84
84
+
s.parse()
85
85
+
.with_context(|| anyhow!("failed to parse present count {s:?}"))
86
86
+
}))?
87
87
+
.map_err(|e| anyhow!("wrong number of presents: expected 6, got {}", e.len()))?;
88
88
+
89
89
+
Ok(Self {
90
90
+
dimensions,
91
91
+
presents,
92
92
+
})
93
93
+
}
94
94
+
}
95
95
+
96
96
+
impl Problem {
97
97
+
fn area(&self) -> usize {
98
98
+
self.dimensions[0] * self.dimensions[1]
99
99
+
}
100
100
+
}
101
101
+
102
102
+
fn rule_out_due_to_insufficient_area(problem: &Problem) -> Option<bool> {
103
103
+
const AREA: [usize; 6] = [5, 7, 7, 7, 6, 7];
104
104
+
105
105
+
let required_area: usize = problem
106
106
+
.presents
107
107
+
.iter()
108
108
+
.zip(AREA)
109
109
+
.map(|(count, area)| count * area)
110
110
+
.sum();
111
111
+
112
112
+
(required_area > problem.area()).then_some(false)
113
113
+
}
114
114
+
115
115
+
fn verify_with_trivial_packing(problem: &Problem) -> Option<bool> {
116
116
+
let available_cells = (problem.dimensions[0] / 3) * (problem.dimensions[1] / 3);
117
117
+
118
118
+
let total_presents: usize = problem.presents.iter().sum();
119
119
+
120
120
+
(total_presents <= available_cells).then_some(true)
121
121
+
}
+2
-2
aoc_2025/src/main.rs
···
11
11
mod day09;
12
12
mod day10;
13
13
mod day11;
14
14
-
// mod day12;
14
14
+
mod day12;
15
15
16
16
use aoc_companion::prelude::*;
17
17
···
29
29
door!(2025-12-09 ~> day09),
30
30
door!(2025-12-10 ~> day10),
31
31
door!(2025-12-11 ~> day11),
32
32
-
// door!(2025-12-12 ~> day12),
32
32
+
door!(2025-12-12 ~> day12),
33
33
])
34
34
.await
35
35
}