This commit is contained in:
Daniel Flanagan 2024-12-02 10:03:03 -06:00
parent a13b049276
commit 242f285231
3 changed files with 45 additions and 101 deletions

View file

@ -1,6 +1,7 @@
[package] [package]
name = "aoc2024" name = "aoc2024"
version = "1.0.0" version = "1.0.0"
edition = "2021"
[[bin]] [[bin]]
name = "day1" name = "day1"

View file

@ -1,66 +1,47 @@
use std::{collections::HashMap, iter::zip, num::ParseIntError, str::FromStr};
use crate::prelude::*;
mod prelude; mod prelude;
pub use crate::prelude::*;
fn main() { fn main() -> EmptyResult {
Day1::show(day_input(1), day_input(1)) let mut note: HistorianNote = day_input(1).parse()?;
show_answers(note.smallests_distances(), note.similarity_score());
Ok(())
} }
struct Day1 {} struct HistorianNote {
impl Day1 { left: Vec<i64>,
fn smallests_distances(input: &str) -> i64 { right: Vec<i64>,
let mut lefts = Vec::<i64>::new(); }
let mut rights = Vec::<i64>::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
}
fn similarity_score(input: &str) -> i64 { impl std::str::FromStr for HistorianNote {
let mut lefts = Vec::<i64>::new(); type Err = std::num::ParseIntError;
let mut rights: HashMap<i64, i64> = HashMap::new();
for l in input.lines() { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut left = Vec::<i64>::new();
let mut right = Vec::<i64>::new();
for l in s.lines() {
let mut tokens = l.split(" "); let mut tokens = l.split(" ");
lefts.push(tokens.next().unwrap().parse().unwrap()); left.push(tokens.next().unwrap_or("").parse()?);
let right: i64 = tokens.next().unwrap().parse().unwrap(); right.push(tokens.next().unwrap_or("").parse()?);
match rights.get_mut(&right) {
None => {
rights.insert(right, 1);
}
Some(n) => {
*n += 1;
}
};
} }
let mut sum = 0; Ok(Self { left, right })
for l in &lefts {
sum += (rights.get(l).unwrap_or(&0) * l)
}
sum
} }
} }
impl AoCSolution for Day1 { impl HistorianNote {
type Input = String; fn smallests_distances(&mut self) -> i64 {
type Solution = i64; self.left.sort();
self.right.sort();
fn part1(input: Self::Input) -> Self::Solution { std::iter::zip(&self.left, &self.right).fold(0, |sum, (l, r)| sum + (l - r).abs())
Day1::smallests_distances(&input)
} }
fn part2(input: Self::Input) -> Self::Solution { fn similarity_score(&self) -> i64 {
Day1::similarity_score(&input) 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::*; use super::*;
#[test] #[test]
fn test() { fn test() -> EmptyResult {
assert_eq!( let mut note: HistorianNote = "3 4\n4 3\n2 5\n1 3\n3 9\n3 3".parse()?;
Day1::part1( assert_eq!(note.smallests_distances(), 11);
r#"3 4 assert_eq!(note.similarity_score(), 31);
4 3 Ok(())
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
);
} }
} }

View file

@ -1,9 +1,13 @@
pub use std::collections::HashMap;
pub use std::fs::File; pub use std::fs::File;
pub use std::io::Read; pub use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{env, io}; use std::{env, io};
pub type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub type EmptyResult = AnyResult<()>;
#[derive(Debug)] #[derive(Debug)]
enum InputFileError { enum InputFileError {
#[allow(dead_code)] #[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()) File::open(&f).expect(format!("Failed to open file {}", f.display()).as_str())
} }
pub trait AoCSolution { pub fn show_answers<T: std::fmt::Display>(part1: T, part2: T) {
type Input; println!("Part 1: {}", part1);
type Solution: std::fmt::Debug + std::cmp::PartialEq + std::fmt::Display; println!("Part 2: {}", part2);
// 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);
}
} }