From 242f285231f42f642c22535814dbbeb94525f529 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Mon, 2 Dec 2024 10:03:03 -0600 Subject: [PATCH] Cleanup --- 2024/rust/Cargo.toml | 1 + 2024/rust/src/day1.rs | 113 +++++++++++++-------------------------- 2024/rust/src/prelude.rs | 32 +++-------- 3 files changed, 45 insertions(+), 101 deletions(-) diff --git a/2024/rust/Cargo.toml b/2024/rust/Cargo.toml index 2581d11..7e8931a 100644 --- a/2024/rust/Cargo.toml +++ b/2024/rust/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "aoc2024" version = "1.0.0" +edition = "2021" [[bin]] name = "day1" diff --git a/2024/rust/src/day1.rs b/2024/rust/src/day1.rs index 45f9357..fa244fa 100644 --- a/2024/rust/src/day1.rs +++ b/2024/rust/src/day1.rs @@ -1,66 +1,47 @@ -use std::{collections::HashMap, iter::zip, num::ParseIntError, str::FromStr}; - -use crate::prelude::*; - mod prelude; +pub use crate::prelude::*; -fn main() { - Day1::show(day_input(1), day_input(1)) +fn main() -> EmptyResult { + let mut note: HistorianNote = day_input(1).parse()?; + show_answers(note.smallests_distances(), note.similarity_score()); + Ok(()) } -struct Day1 {} -impl Day1 { - fn smallests_distances(input: &str) -> i64 { - let mut lefts = Vec::::new(); - let mut rights = Vec::::new(); - for l in input.lines() { - let mut tokens = l.split(" "); - lefts.push(tokens.next().unwrap().parse().unwrap()); - rights.push(tokens.next().unwrap().parse().unwrap()); - } - lefts.sort(); - rights.sort(); - let mut sum = 0; - for (l, r) in zip(&lefts, &rights) { - sum += (l - r).abs(); - } - sum - } +struct HistorianNote { + left: Vec, + right: Vec, +} - fn similarity_score(input: &str) -> i64 { - let mut lefts = Vec::::new(); - let mut rights: HashMap = HashMap::new(); - for l in input.lines() { +impl std::str::FromStr for HistorianNote { + type Err = std::num::ParseIntError; + + fn from_str(s: &str) -> Result { + let mut left = Vec::::new(); + let mut right = Vec::::new(); + for l in s.lines() { let mut tokens = l.split(" "); - lefts.push(tokens.next().unwrap().parse().unwrap()); - let right: i64 = tokens.next().unwrap().parse().unwrap(); - match rights.get_mut(&right) { - None => { - rights.insert(right, 1); - } - Some(n) => { - *n += 1; - } - }; + left.push(tokens.next().unwrap_or("").parse()?); + right.push(tokens.next().unwrap_or("").parse()?); } - let mut sum = 0; - for l in &lefts { - sum += (rights.get(l).unwrap_or(&0) * l) - } - sum + Ok(Self { left, right }) } } -impl AoCSolution for Day1 { - type Input = String; - type Solution = i64; - - fn part1(input: Self::Input) -> Self::Solution { - Day1::smallests_distances(&input) +impl HistorianNote { + fn smallests_distances(&mut self) -> i64 { + self.left.sort(); + self.right.sort(); + std::iter::zip(&self.left, &self.right).fold(0, |sum, (l, r)| sum + (l - r).abs()) } - fn part2(input: Self::Input) -> Self::Solution { - Day1::similarity_score(&input) + fn similarity_score(&self) -> i64 { + let frequencies = self.right.iter().fold(HashMap::new(), |mut map, r| { + *map.entry(r).or_default() += 1; + map + }); + self.left + .iter() + .fold(0, |sum, l| sum + frequencies.get(l).unwrap_or(&0) * l) } } @@ -69,30 +50,10 @@ mod tests { use super::*; #[test] - fn test() { - assert_eq!( - Day1::part1( - r#"3 4 -4 3 -2 5 -1 3 -3 9 -3 3"# - .into() - ), - 11 - ); - assert_eq!( - Day1::part2( - r#"3 4 -4 3 -2 5 -1 3 -3 9 -3 3"# - .into() - ), - 31 - ); + fn test() -> EmptyResult { + let mut note: HistorianNote = "3 4\n4 3\n2 5\n1 3\n3 9\n3 3".parse()?; + assert_eq!(note.smallests_distances(), 11); + assert_eq!(note.similarity_score(), 31); + Ok(()) } } diff --git a/2024/rust/src/prelude.rs b/2024/rust/src/prelude.rs index 8086f69..b31ef9e 100644 --- a/2024/rust/src/prelude.rs +++ b/2024/rust/src/prelude.rs @@ -1,9 +1,13 @@ +pub use std::collections::HashMap; pub use std::fs::File; pub use std::io::Read; use std::path::{Path, PathBuf}; use std::{env, io}; +pub type AnyResult = std::result::Result>; +pub type EmptyResult = AnyResult<()>; + #[derive(Debug)] enum InputFileError { #[allow(dead_code)] @@ -42,29 +46,7 @@ pub fn day_input_file(day: u8) -> File { File::open(&f).expect(format!("Failed to open file {}", f.display()).as_str()) } -pub trait AoCSolution { - type Input; - type Solution: std::fmt::Debug + std::cmp::PartialEq + std::fmt::Display; - - // Are part1 and part2 solutions _always_ the same? - fn part1(input: Self::Input) -> Self::Solution; - fn part2(input: Self::Input) -> Self::Solution; - - fn show(i1: Self::Input, i2: Self::Input) { - println!("Part 1: {}", Self::part1(i1)); - println!("Part 2: {}", Self::part2(i2)); - } -} - -#[cfg(test)] -pub trait AoCSolutionTest: AoCSolution { - const PART1_TEST_INPUT: Self::Input; - const PART2_TEST_INPUT: Self::Input; - const PART1_TEST_RESULT: Self::Solution; - const PART2_TEST_RESULT: Self::Solution; - - fn assert_valid() { - assert_eq!(Self::part1(Self::PART1_TEST_INPUT), Self::PART1_TEST_RESULT); - assert_eq!(Self::part2(Self::PART2_TEST_INPUT), Self::PART2_TEST_RESULT); - } +pub fn show_answers(part1: T, part2: T) { + println!("Part 1: {}", part1); + println!("Part 2: {}", part2); }