Skip to content

Commit 32cd9df

Browse files
committed
solve: day13
1 parent b3df4b9 commit 32cd9df

File tree

6 files changed

+281
-1
lines changed

6 files changed

+281
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
resolver = "2"
33
members = [
44
"utils",
5-
"aoc_derive", "day1", "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9", "day10", "day11", "day12",
5+
"aoc_derive", "day1", "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9", "day10", "day11", "day12", "day13",
66
]
77

88
[workspace.dependencies]

day12/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "day12"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
aoc_derive.path = '../aoc_derive'
8+
utils.path = '../utils'
9+
derive_more.workspace = true
10+
itertools.workspace = true
11+
lazy-regex.workspace = true
12+
parse-display.workspace = true
13+
rayon.workspace = true
14+
regex.workspace = true
15+
num.workspace = true
16+
17+
[dev-dependencies]
18+
pretty_assertions.workspace = true

day12/src/main.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use std::collections::{HashMap, HashSet};
2+
3+
use aoc_derive::aoc_main;
4+
use graphs::{UnweightedGraph, floodfill};
5+
use math::Vec2D;
6+
use utils::*;
7+
8+
#[derive(Debug, derive_more::From, derive_more::Deref, derive_more::DerefMut)]
9+
struct Map(grid::Grid<char>);
10+
11+
impl UnweightedGraph for Map {
12+
type Node = Vec2D;
13+
14+
fn neighbors<'a, 'b: 'a>(&'a self, node: &'b Vec2D) -> impl Iterator<Item = Vec2D> + 'a {
15+
node.orthogonal_neighbors().filter(|n| self.get(*n) == Some(&self[*node]))
16+
}
17+
}
18+
19+
fn sides(region: &HashSet<Vec2D>, map: &Map) -> usize {
20+
let c = map[*region.iter().next().unwrap()];
21+
22+
let edges = region.iter().filter(|p| p.orthogonal_neighbors().any(|n| map.get(n) != Some(&c)));
23+
24+
let mut visited = HashSet::new();
25+
26+
let mut sides = 0;
27+
28+
for edge in edges {
29+
if visited.contains(edge) {
30+
continue;
31+
}
32+
visited.insert(*edge);
33+
34+
let edge_normal = edge
35+
.orthogonal_neighbors()
36+
.find_map(|n| (map.get(n) != Some(&c)).then_some(n - *edge))
37+
.unwrap();
38+
39+
let mut pos = *edge;
40+
let mut normal = edge_normal;
41+
42+
loop {
43+
if !region.contains(&(pos + normal))
44+
&& !region.contains(&(pos + normal.rotated_right()))
45+
{
46+
normal = normal.rotated_right();
47+
sides += 1;
48+
} else if region.contains(&(pos + normal.rotated_right()))
49+
&& region.contains(&(pos + normal.rotated_right() + normal))
50+
{
51+
pos = pos + normal.rotated_right() + normal;
52+
normal = normal.rotated_left();
53+
sides += 1;
54+
} else {
55+
pos += normal.rotated_right();
56+
}
57+
58+
visited.insert(pos);
59+
60+
if pos == *edge && normal == edge_normal {
61+
break;
62+
}
63+
}
64+
}
65+
66+
sides
67+
}
68+
69+
#[aoc_main]
70+
fn solve(input: Input) -> impl Into<Solution> {
71+
let map = Map::from(input.char_grid());
72+
73+
let mut region_map = HashMap::<char, Vec<HashSet<Vec2D>>>::new();
74+
75+
for (pos, c) in map.iter() {
76+
let regions = region_map.entry(*c).or_default();
77+
78+
if !regions.iter().any(|r| r.contains(&pos)) {
79+
regions.push(floodfill(&map, pos).into_iter().collect());
80+
}
81+
}
82+
83+
let (part1, part2) = region_map
84+
.iter()
85+
.flat_map(|(c, regions)| {
86+
regions.iter().map(|region| {
87+
let area = region.len();
88+
let perimeter = region
89+
.iter()
90+
.map(|pos| {
91+
pos.orthogonal_neighbors().filter(|n| map.get(*n) != Some(c)).count()
92+
})
93+
.sum_usize();
94+
(area * perimeter, area * sides(region, &map))
95+
})
96+
})
97+
.unzip_vec();
98+
99+
(part1.into_iter().sum_usize(), part2.into_iter().sum_usize())
100+
}
101+
102+
#[cfg(test)]
103+
mod tests {
104+
use super::*;
105+
#[test]
106+
fn test_examples() {
107+
use utils::assert_example;
108+
assert_example!(
109+
r#"
110+
AAAA
111+
BBCD
112+
BBCC
113+
EEEC"#,
114+
140,
115+
80
116+
);
117+
118+
assert_example!(
119+
"OOOOO
120+
OXOXO
121+
OOOOO
122+
OXOXO
123+
OOOOO",
124+
772
125+
);
126+
127+
assert_example!(
128+
"RRRRIICCFF
129+
RRRRIICCCF
130+
VVRRRCCFFF
131+
VVRCCCJFFF
132+
VVVVCJJCFE
133+
VVIVCCJJEE
134+
VVIIICJJEE
135+
MIIIIIJJEE
136+
MIIISIJEEE
137+
MMMISSJEEE",
138+
1930,
139+
1206
140+
);
141+
142+
assert_part2!(
143+
"EEEEE
144+
EXXXX
145+
EEEEE
146+
EXXXX
147+
EEEEE",
148+
236
149+
);
150+
151+
assert_part2!(
152+
"AAAAAA
153+
AAABBA
154+
AAABBA
155+
ABBAAA
156+
ABBAAA
157+
AAAAAA",
158+
368
159+
);
160+
}
161+
}

day13/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "day13"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
aoc_derive.path = '../aoc_derive'
8+
utils.path = '../utils'
9+
derive_more.workspace = true
10+
itertools.workspace = true
11+
lazy-regex.workspace = true
12+
parse-display.workspace = true
13+
rayon.workspace = true
14+
regex.workspace = true
15+
num.workspace = true
16+
17+
[dev-dependencies]
18+
pretty_assertions.workspace = true

day13/src/main.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::str::FromStr;
2+
3+
use aoc_derive::aoc_main;
4+
use itertools::Itertools;
5+
use math::Vec2D;
6+
use utils::*;
7+
8+
#[derive(Debug, Clone)]
9+
struct Machine {
10+
a: Vec2D,
11+
b: Vec2D,
12+
prize: Vec2D,
13+
}
14+
15+
impl FromStr for Machine {
16+
type Err = ();
17+
18+
fn from_str(s: &str) -> Result<Self, Self::Err> {
19+
let (a, b, prize) = s.lines().map(extract_two_numbers).collect_tuple().unwrap();
20+
Ok(Self { a, b, prize })
21+
}
22+
}
23+
24+
impl Machine {
25+
fn into_part2(mut self) -> Self {
26+
self.prize += Vec2D::new(10000000000000_i64, 10000000000000_i64);
27+
self
28+
}
29+
30+
fn solve(&self) -> Option<i64> {
31+
let denominator = self.a.x * self.prize.y - self.prize.x * self.a.y;
32+
let numerator = self.a.x * self.b.y - self.b.x * self.a.y;
33+
34+
(denominator % numerator == 0).then_some(denominator / numerator).and_then(|num_b| {
35+
let denominator = self.prize.x - num_b * self.b.x;
36+
(denominator % self.a.x == 0).then_some(num_b + 3 * denominator / self.a.x)
37+
})
38+
}
39+
}
40+
41+
#[aoc_main]
42+
fn solve(input: Input) -> impl Into<Solution> {
43+
let machines = input.blocks().flat_map(Machine::from_str).collect_vec();
44+
45+
(
46+
machines.iter().flat_map(Machine::solve).sum_usize(),
47+
machines.into_iter().flat_map(|m| m.into_part2().solve()).sum_usize(),
48+
)
49+
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
use super::*;
54+
#[test]
55+
fn test_examples() {
56+
use utils::assert_example;
57+
assert_example!(
58+
r#"Button A: X+94, Y+34
59+
Button B: X+22, Y+67
60+
Prize: X=8400, Y=5400
61+
62+
Button A: X+26, Y+66
63+
Button B: X+67, Y+21
64+
Prize: X=12748, Y=12176
65+
66+
Button A: X+17, Y+86
67+
Button B: X+84, Y+37
68+
Prize: X=7870, Y=6450
69+
70+
Button A: X+69, Y+23
71+
Button B: X+27, Y+71
72+
Prize: X=18641, Y=10279
73+
"#,
74+
480
75+
);
76+
}
77+
}

utils/src/regex_helper.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::str::FromStr;
33
use itertools::Itertools;
44
use lazy_regex::regex;
55

6+
use crate::math::Vec2D;
7+
68
pub trait RegexHelper {
79
/// shortcut for find_iter followed by a map to str
810
fn find_iter_str<'a, 'b: 'a>(&'a self, s: &'b str) -> impl Iterator<Item = &'b str> + 'a;
@@ -50,6 +52,10 @@ pub fn extract_numbers_unsigned(s: &str) -> impl Iterator<Item = usize> + '_ {
5052
extract_numbers::<usize>(s)
5153
}
5254

55+
pub fn extract_two_numbers(s: &str) -> Vec2D {
56+
extract_numbers::<i64>(s).collect_tuple::<(_, _)>().unwrap().into()
57+
}
58+
5359
impl RegexHelper for regex::Regex {
5460
fn find_iter_str<'a, 'b: 'a>(&'a self, s: &'b str) -> impl Iterator<Item = &'b str> + 'a {
5561
self.find_iter(s).map(|m| m.as_str())

0 commit comments

Comments
 (0)