Skip to content

Commit 22bbc4a

Browse files
committed
2023 Day 3.
1 parent 363abdf commit 22bbc4a

File tree

9 files changed

+317
-0
lines changed

9 files changed

+317
-0
lines changed

2023/03/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "y2023d03"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]

2023/03/data/example1

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
467..114..
2+
...*......
3+
..35..633.
4+
......#...
5+
617*......
6+
.....+.58.
7+
..592.....
8+
......755.
9+
...$.*....
10+
.664.598..

2023/03/data/input

Lines changed: 140 additions & 0 deletions
Large diffs are not rendered by default.

2023/03/src/data.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[cfg(test)]
2+
pub const EXAMPLE1: &'static str = include_str!("../data/example1");
3+
4+
#[allow(unused)]
5+
pub const INPUT: &'static str = include_str!("../data/input");

2023/03/src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
mod data;
2+
mod part1;
3+
mod part2;
4+
mod puzzle;
5+
6+
fn main() {
7+
use data::INPUT;
8+
println!("Part 1: {}", part1::run(INPUT));
9+
println!("Part 2: {}", part2::run(INPUT));
10+
}

2023/03/src/part1/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use crate::puzzle::{Part, Puzzle, Symbol};
2+
use std::collections::HashMap;
3+
4+
fn adjacent_to_symbol(part: &Part, symbols: &HashMap<(usize, usize), Symbol>) -> bool {
5+
part.adjacent_points()
6+
.into_iter()
7+
.any(|pos| symbols.contains_key(&pos))
8+
}
9+
10+
pub fn run(input: &str) -> u32 {
11+
let puzzle: Puzzle = input.parse().expect("parse failed");
12+
puzzle
13+
.parts
14+
.into_iter()
15+
.filter(|part| adjacent_to_symbol(part, &puzzle.symbols))
16+
.map(|part| part.number)
17+
.sum()
18+
}
19+
20+
#[cfg(test)]
21+
mod test {
22+
use super::*;
23+
use crate::data::EXAMPLE1;
24+
25+
#[test]
26+
fn test1() {
27+
assert_eq!(run(EXAMPLE1), 4361);
28+
}
29+
}

2023/03/src/part2/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::puzzle::{Part, Puzzle, Symbol};
2+
use std::collections::HashMap;
3+
4+
fn adjacent_gears(
5+
part: &Part,
6+
symbols: &HashMap<(usize, usize), Symbol>,
7+
) -> impl IntoIterator<Item = (usize, usize)> {
8+
part.adjacent_points()
9+
.into_iter()
10+
.filter(|pos| symbols.get(pos).is_some_and(Symbol::is_gear))
11+
}
12+
13+
pub fn run(input: &str) -> u32 {
14+
let puzzle: Puzzle = input.parse().expect("parse failed");
15+
let mut gears: HashMap<(usize, usize), Vec<&Part>> = <_>::default();
16+
for part in &puzzle.parts {
17+
for gear in adjacent_gears(part, &puzzle.symbols) {
18+
gears.entry(gear).or_default().push(part);
19+
}
20+
}
21+
gears
22+
.values()
23+
.filter(|parts| parts.len() == 2)
24+
.map(|parts| parts[0].number * parts[1].number)
25+
.sum()
26+
}
27+
28+
#[cfg(test)]
29+
mod test {
30+
use super::*;
31+
use crate::data::EXAMPLE1;
32+
33+
#[test]
34+
fn test1() {
35+
assert_eq!(run(EXAMPLE1), 467835);
36+
}
37+
}

2023/03/src/puzzle.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use std::collections::HashMap;
2+
use std::str::FromStr;
3+
4+
#[derive(Debug)]
5+
pub struct ParseError;
6+
7+
#[derive(Debug, Default)]
8+
pub struct Puzzle {
9+
pub parts: Vec<Part>,
10+
pub symbols: HashMap<(usize, usize), Symbol>,
11+
}
12+
13+
impl FromStr for Puzzle {
14+
type Err = ParseError;
15+
16+
fn from_str(s: &str) -> Result<Self, Self::Err> {
17+
let mut puzzle = Puzzle::default();
18+
for (row, line) in s.lines().enumerate() {
19+
let mut parsing_part = false;
20+
for (col, ch) in line.chars().enumerate() {
21+
match ch {
22+
'0'..='9' => {
23+
if !parsing_part {
24+
parsing_part = true;
25+
puzzle.parts.push(Part {
26+
number: 0,
27+
row,
28+
col,
29+
len: 0,
30+
});
31+
}
32+
let part = puzzle.parts.last_mut().unwrap();
33+
part.number = part.number * 10 + ch.to_digit(10).unwrap();
34+
part.len += 1;
35+
}
36+
'.' => parsing_part = false,
37+
_ => {
38+
puzzle.symbols.insert((row, col), Symbol(ch));
39+
parsing_part = false;
40+
}
41+
}
42+
}
43+
}
44+
Ok(puzzle)
45+
}
46+
}
47+
48+
#[derive(Debug)]
49+
pub struct Part {
50+
pub number: u32,
51+
pub row: usize,
52+
pub col: usize,
53+
pub len: usize,
54+
}
55+
56+
impl Part {
57+
pub fn adjacent_points(&self) -> impl IntoIterator<Item = (usize, usize)> {
58+
let min_col = self.col.saturating_sub(1);
59+
let max_col = self.col + self.len;
60+
let min_row = self.row.saturating_sub(1);
61+
let max_row = self.row + 1;
62+
let top = (min_col..=max_col).map(move |col| (min_row, col));
63+
let mid = [min_col, max_col].map(|col| (self.row, col));
64+
let bot = (min_col..=max_col).map(move |col| (max_row, col));
65+
top.chain(mid).chain(bot)
66+
}
67+
}
68+
69+
#[derive(Debug)]
70+
pub struct Symbol(char);
71+
72+
impl Symbol {
73+
pub fn is_gear(&self) -> bool {
74+
self.0 == '*'
75+
}
76+
}

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)