1
0

Add Solver trait and move common functionality out of concrete solvers

This commit is contained in:
Stefan Müller
2025-12-06 01:27:15 +01:00
parent e8a91164d3
commit 6a7bffa42a
5 changed files with 119 additions and 116 deletions
+5 -3
View File
@@ -1,8 +1,10 @@
mod solvers;
mod common; mod common;
mod solvers;
fn main() { fn main() {
println!("### Advent of Code 2025 ###\n"); println!("### Advent of Code 2025 ###\n");
solvers::printing_department::PrintingDepartment::new().run(); solvers::run(Box::new(
solvers::cafeteria::Cafeteria::new().run(); solvers::printing_department::PrintingDepartment::new(),
));
solvers::run(Box::new(solvers::cafeteria::Cafeteria::new()));
} }
+75
View File
@@ -1,2 +1,77 @@
use std::fs::File;
use std::io;
use std::io::BufRead;
use std::path::Path;
pub mod cafeteria; pub mod cafeteria;
pub mod printing_department; pub mod printing_department;
pub trait Solver {
fn get_puzzle_index(&self) -> u8;
fn get_puzzle_name(&self) -> &str;
fn get_input_filename(&self) -> &str;
fn get_part1(&self) -> u64;
fn get_part2(&self) -> u64;
fn process_data(&mut self, lines: io::Lines<io::BufReader<File>>);
}
pub fn run(mut solver: Box<dyn Solver>) {
println!(
"--- Day {}: {} ---",
solver.get_puzzle_index(),
solver.get_puzzle_name()
);
// The "../../data" and "../../../data" paths are useful for running the binary from
// "target/release/" directory with "data/" directory in package root or parent directory.
let paths = vec!["./data", "../data", "../../data", "../../../data"];
match read_data_file(solver.get_input_filename(), &paths) {
Ok(lines) => {
solver.process_data(lines);
print_result(solver);
}
Err(error) => eprintln!("{}", error),
};
}
fn print_result(solver: Box<dyn Solver>) {
println!(
"Part 1: {:?}\nPart 2: {:?}\n",
solver.get_part1(),
solver.get_part2()
);
}
fn read_data_file<T>(
filename: T,
search_paths: &Vec<T>,
) -> Result<io::Lines<io::BufReader<File>>, String>
where
T: AsRef<Path>,
{
for path in search_paths {
if let Ok(lines) = read_lines(path.as_ref().join(&filename)) {
return Ok(lines);
}
}
let message = search_paths
.iter()
.map(|path| path.as_ref().join(&filename).display().to_string())
.collect::<Vec<_>>()
.join("\n");
Err(format!(
"Cannot open puzzle input file, searched these paths:\n{}",
message
))
}
// 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>>>
where
T: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
+17 -54
View File
@@ -1,36 +1,30 @@
use crate::common::interval::Interval; use crate::common::interval::Interval;
use crate::solvers::Solver;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::BufRead;
use std::path::Path;
pub struct Cafeteria { pub struct Cafeteria {
part1: u64, part1: u64,
part2: u64, part2: u64,
} }
impl Cafeteria { impl Solver for Cafeteria {
pub fn new() -> Cafeteria { fn get_puzzle_index(&self) -> u8 {
Cafeteria { part1: 0, part2: 0 } 5
} }
fn get_puzzle_name(&self) -> &str {
pub fn run(&mut self) { "Cafeteria"
println!("--- Day 5: Cafeteria ---");
// The "../../data" and "../../../data" paths are useful for running the binary from
// "target/release/" directory with "data/" directory in package root or parent directory.
let paths = vec!["./data", "../data", "../../data", "../../../data"];
match read_data_file("cafeteria.txt", &paths) {
Ok(lines) => {
self.process_data(lines);
self.print_result();
} }
Err(error) => eprintln!("{}", error), 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>>) { 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();
@@ -83,41 +77,10 @@ impl Cafeteria {
self.part2 = intervals.iter().map(|x| x.len()).sum(); self.part2 = intervals.iter().map(|x| x.len()).sum();
} }
fn print_result(&self) {
println!("Part 1: {:?}\nPart 2: {:?}\n", self.part1, self.part2);
}
} }
fn read_data_file<T>( impl Cafeteria {
filename: T, pub fn new() -> Cafeteria {
search_paths: &Vec<T>, Cafeteria { part1: 0, part2: 0 }
) -> Result<io::Lines<io::BufReader<File>>, String>
where
T: AsRef<Path>,
{
for path in search_paths {
if let Ok(lines) = read_lines(path.as_ref().join(&filename)) {
return Ok(lines);
} }
} }
let message = search_paths
.iter()
.map(|path| path.as_ref().join(&filename).display().to_string())
.collect::<Vec<_>>()
.join("\n");
Err(format!(
"Cannot open puzzle input file, searched these paths:\n{}",
message
))
}
// 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>>>
where
T: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
+17 -54
View File
@@ -1,35 +1,29 @@
use crate::solvers::Solver;
use grid::*; use grid::*;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::BufRead;
use std::path::Path;
pub struct PrintingDepartment { pub struct PrintingDepartment {
part1: u64, part1: u64,
part2: u64, part2: u64,
} }
impl PrintingDepartment { impl Solver for PrintingDepartment {
pub fn new() -> PrintingDepartment { fn get_puzzle_index(&self) -> u8 {
PrintingDepartment { part1: 0, part2: 0 } 4
} }
fn get_puzzle_name(&self) -> &str {
pub fn run(&mut self) { "Printing Department"
println!("--- Day 4: Printing Department ---");
// The "../../data" and "../../../data" paths are useful for running the binary from
// "target/release/" directory with "data/" directory in package root or parent directory.
let paths = vec!["./data", "../data", "../../data", "../../../data"];
match read_data_file("printing_department.txt", &paths) {
Ok(lines) => {
self.process_data(lines);
self.print_result();
} }
Err(error) => eprintln!("{}", error), 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>>) { 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 lines.map_while(Result::ok) {
@@ -62,41 +56,10 @@ impl PrintingDepartment {
} }
} }
} }
fn print_result(&self) {
println!("Part 1: {:?}\nPart 2: {:?}\n", self.part1, self.part2);
}
} }
fn read_data_file<T>( impl PrintingDepartment {
filename: T, pub fn new() -> PrintingDepartment {
search_paths: &Vec<T>, PrintingDepartment { part1: 0, part2: 0 }
) -> Result<io::Lines<io::BufReader<File>>, String>
where
T: AsRef<Path>,
{
for path in search_paths {
if let Ok(lines) = read_lines(path.as_ref().join(&filename)) {
return Ok(lines);
} }
} }
let message = search_paths
.iter()
.map(|path| path.as_ref().join(&filename).display().to_string())
.collect::<Vec<_>>()
.join("\n");
Err(format!(
"Cannot open puzzle input file, searched these paths:\n{}",
message
))
}
// 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>>>
where
T: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}