1
0

Refactor Solver trait to streamline usage

This commit is contained in:
Stefan Müller
2025-12-08 14:38:02 +01:00
parent ad64b05db7
commit 496293b33b
5 changed files with 63 additions and 123 deletions
+3 -5
View File
@@ -3,9 +3,7 @@ mod solvers;
fn main() { fn main() {
println!("### Advent of Code 2025 ###\n"); println!("### Advent of Code 2025 ###\n");
solvers::run(Box::new( solvers::run(solvers::printing_department::PrintingDepartment{});
solvers::printing_department::PrintingDepartment::new(), solvers::run(solvers::cafeteria::Cafeteria{});
)); solvers::run(solvers::trash_compactor::TrashCompactor {});
solvers::run(Box::new(solvers::cafeteria::Cafeteria::new()));
solvers::run(Box::new(solvers::trash_compactor::TrashCompactor::new()));
} }
+25 -24
View File
@@ -8,52 +8,53 @@ pub mod printing_department;
pub mod trash_compactor; pub mod trash_compactor;
pub trait Solver { pub trait Solver {
fn get_puzzle_index(&self) -> u8; const PUZZLE_INDEX: u8;
fn get_puzzle_name(&self) -> &str; const PUZZLE_NAME: &'static str;
fn get_input_filename(&self) -> &str; // TODO: Replace constant input filename by a conversion from the puzzle name in the runner.
fn get_part1(&self) -> u64; const INPUT_FILENAME: &'static str;
fn get_part2(&self) -> u64;
fn process_data(&mut self, lines: io::Lines<io::BufReader<File>>); fn puzzle_index(&self) -> u8 { Self::PUZZLE_INDEX }
fn puzzle_name(&self) -> &'static str { Self::PUZZLE_NAME }
fn input_filename(&self) -> &'static str { Self::INPUT_FILENAME }
fn process_data<R: BufRead>(&self, reader: R) -> (u64, u64);
} }
pub fn run(mut solver: Box<dyn Solver>) { pub fn run<S: Solver>(solver: S) {
println!( println!(
"--- Day {}: {} ---", "--- Day {}: {} ---",
solver.get_puzzle_index(), solver.puzzle_index(),
solver.get_puzzle_name() solver.puzzle_name()
); );
// TODO: Convert to constant and add "example" paths.
// The "../../data" and "../../../data" paths are useful for running the binary from // The "../../data" and "../../../data" paths are useful for running the binary from
// "target/release/" directory with "data/" directory in package root or parent directory. // "target/release/" directory with "data/" directory in package root or parent directory.
let paths = vec!["./data", "../data", "../../data", "../../../data"]; let paths = vec!["./data", "../data", "../../data", "../../../data"];
match read_data_file(solver.get_input_filename(), &paths) { match read_data_file(solver.input_filename(), &paths) {
Ok(lines) => { Ok(reader) => {
solver.process_data(lines); let result = solver.process_data(reader);
print_result(solver); print_result(result);
} }
Err(error) => eprintln!("{}", error), Err(error) => eprintln!("{}", error),
}; };
} }
fn print_result(solver: Box<dyn Solver>) { fn print_result(result: (u64, u64)) {
println!( println!("Part 1: {:?}\nPart 2: {:?}\n", result.0, result.1);
"Part 1: {:?}\nPart 2: {:?}\n",
solver.get_part1(),
solver.get_part2()
);
} }
fn read_data_file<T>( fn read_data_file<T>(
filename: T, filename: T,
search_paths: &Vec<T>, search_paths: &Vec<T>,
) -> Result<io::Lines<io::BufReader<File>>, String> ) -> Result<io::BufReader<File>, String>
where where
T: AsRef<Path>, T: AsRef<Path>,
{ {
for path in search_paths { for path in search_paths {
if let Ok(lines) = read_lines(path.as_ref().join(&filename)) { if let Ok(reader) = read_file(path.as_ref().join(&filename)) {
return Ok(lines); return Ok(reader);
} }
} }
@@ -69,10 +70,10 @@ where
} }
// This code is taken from https://doc.rust-lang.org/stable/rust-by-example/std_misc/file/read_lines.html // This code is taken from https://doc.rust-lang.org/stable/rust-by-example/std_misc/file/read_lines.html
fn read_lines<T>(filename: T) -> io::Result<io::Lines<io::BufReader<File>>> fn read_file<T>(filename: T) -> io::Result<io::BufReader<File>>
where where
T: AsRef<Path>, T: AsRef<Path>,
{ {
let file = File::open(filename)?; let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines()) Ok(io::BufReader::new(file))
} }
+12 -33
View File
@@ -1,35 +1,19 @@
use crate::common::interval::Interval; use crate::common::interval::Interval;
use crate::solvers::Solver; use crate::solvers::Solver;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fs::File; use std::io::BufRead;
use std::io;
pub struct Cafeteria { pub struct Cafeteria {}
part1: u64,
part2: u64,
}
impl Solver for Cafeteria { impl Solver for Cafeteria {
fn get_puzzle_index(&self) -> u8 { const PUZZLE_INDEX: u8 = 5;
5 const PUZZLE_NAME: &'static str = "Cafeteria";
} const INPUT_FILENAME: &'static str = "cafeteria.txt";
fn get_puzzle_name(&self) -> &str {
"Cafeteria" fn process_data<R: BufRead>(&self, mut reader: R) -> (u64, u64) {
}
fn get_input_filename(&self) -> &str {
"cafeteria.txt"
}
fn get_part1(&self) -> u64 {
self.part1
}
fn get_part2(&self) -> u64 {
self.part2
}
fn process_data(&mut self, mut lines: io::Lines<io::BufReader<File>>) {
// Builds intervals collection. // Builds intervals collection.
let mut intervals: BTreeSet<Interval> = BTreeSet::new(); let mut intervals: BTreeSet<Interval> = BTreeSet::new();
for line in lines for line in reader.by_ref().lines()
.by_ref()
.map_while(|x| x.ok().filter(|s| !s.is_empty())) .map_while(|x| x.ok().filter(|s| !s.is_empty()))
{ {
let values: Vec<u64> = line.split('-').map_while(|s| s.parse().ok()).collect(); let values: Vec<u64> = line.split('-').map_while(|s| s.parse().ok()).collect();
@@ -68,19 +52,14 @@ impl Solver for Cafeteria {
} }
} }
let mut part1 = 0;
// Tests values against intervals. // Tests values against intervals.
for value in lines.map_while(|x| x.ok()?.parse::<u64>().ok()) { for value in reader.lines().map_while(|x| x.ok()?.parse::<u64>().ok()) {
if intervals.iter().any(|interval| interval.contains(value)) { if intervals.iter().any(|interval| interval.contains(value)) {
self.part1 += 1; part1 += 1;
} }
} }
self.part2 = intervals.iter().map(|x| x.len()).sum(); (part1, intervals.iter().map(|x| x.len()).sum())
}
}
impl Cafeteria {
pub fn new() -> Cafeteria {
Cafeteria { part1: 0, part2: 0 }
} }
} }
+11 -30
View File
@@ -1,35 +1,21 @@
use crate::solvers::Solver; use crate::solvers::Solver;
use grid::*; use grid::*;
use std::fs::File; use std::io::BufRead;
use std::io;
pub struct PrintingDepartment { pub struct PrintingDepartment {}
part1: u64,
part2: u64,
}
impl Solver for PrintingDepartment { impl Solver for PrintingDepartment {
fn get_puzzle_index(&self) -> u8 { const PUZZLE_INDEX: u8 = 4;
4 const PUZZLE_NAME: &'static str = "Printing Department";
} const INPUT_FILENAME: &'static str = "printing_department.txt";
fn get_puzzle_name(&self) -> &str {
"Printing Department" fn process_data<R: BufRead>(&self, reader: R) -> (u64, u64) {
}
fn get_input_filename(&self) -> &str {
"printing_department.txt"
}
fn get_part1(&self) -> u64 {
self.part1
}
fn get_part2(&self) -> u64 {
self.part2
}
fn process_data(&mut self, lines: io::Lines<io::BufReader<File>>) {
let mut grid = Grid::new(0, 0); let mut grid = Grid::new(0, 0);
for line in lines.map_while(Result::ok) { for line in reader.lines().map_while(Result::ok) {
grid.push_row(line.bytes().map(|c| c == b'@').collect()); grid.push_row(line.bytes().map(|c| c == b'@').collect());
} }
let mut part1 = 0;
'cells: for cell in grid.indexed_iter() { 'cells: for cell in grid.indexed_iter() {
if *cell.1 { if *cell.1 {
let mut count = 0; let mut count = 0;
@@ -52,14 +38,9 @@ impl Solver for PrintingDepartment {
} }
} }
} }
self.part1 += 1; part1 += 1;
} }
} }
} (part1, 0)
}
impl PrintingDepartment {
pub fn new() -> PrintingDepartment {
PrintingDepartment { part1: 0, part2: 0 }
} }
} }
+12 -31
View File
@@ -1,51 +1,32 @@
use crate::solvers::Solver; use crate::solvers::Solver;
use grid::*; use grid::*;
use std::fs::File; use std::io::BufRead;
use std::io;
pub struct TrashCompactor { pub struct TrashCompactor {}
part1: u64,
part2: u64,
}
impl Solver for TrashCompactor { impl Solver for TrashCompactor {
fn get_puzzle_index(&self) -> u8 { const PUZZLE_INDEX: u8 = 6;
6 const PUZZLE_NAME: &'static str = "Trash Compactor";
} const INPUT_FILENAME: &'static str = "trash_compactor.txt";
fn get_puzzle_name(&self) -> &str {
"Trash Compactor" fn process_data<R: BufRead>(&self, reader: R) -> (u64, u64) {
} let mut part1 = 0;
fn get_input_filename(&self) -> &str {
"trash_compactor.txt"
}
fn get_part1(&self) -> u64 {
self.part1
}
fn get_part2(&self) -> u64 {
self.part2
}
fn process_data(&mut self, lines: io::Lines<io::BufReader<File>>) {
let mut grid = Grid::new(0, 0); let mut grid = Grid::new(0, 0);
for line in lines.map_while(Result::ok) { for line in reader.lines().map_while(Result::ok) {
let v: Vec<&str> = line.split(' ').filter(|s| !s.is_empty()).collect(); let v: Vec<&str> = line.split(' ').filter(|s| !s.is_empty()).collect();
match v[0].parse::<u32>() { match v[0].parse::<u32>() {
Ok(_) => grid.push_row(v.iter().map(|n| n.parse::<u64>().unwrap()).collect()), Ok(_) => grid.push_row(v.iter().map(|n| n.parse::<u64>().unwrap()).collect()),
Err(_) => { Err(_) => {
for (i, sign) in v.iter().enumerate() { for (i, sign) in v.iter().enumerate() {
if *sign == "+" { if *sign == "+" {
self.part1 += grid.iter_col(i).sum::<u64>(); part1 += grid.iter_col(i).sum::<u64>();
} else { } else {
self.part1 += grid.iter_col(i).product::<u64>(); part1 += grid.iter_col(i).product::<u64>();
} }
} }
} }
} }
} }
} (part1, 0)
}
impl TrashCompactor {
pub fn new() -> TrashCompactor {
TrashCompactor { part1: 0, part2: 0 }
} }
} }