From 49c37800a0657825ad6f56a4bcd53778f349f17f Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Wed, 4 Dec 2024 22:41:33 -0800 Subject: [PATCH] day5: complete solution --- 5/Cargo.lock | 7 ++ 5/Cargo.toml | 6 ++ 5/src/main.rs | 230 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 5/Cargo.lock create mode 100644 5/Cargo.toml create mode 100644 5/src/main.rs diff --git a/5/Cargo.lock b/5/Cargo.lock new file mode 100644 index 0000000..7a62bb0 --- /dev/null +++ b/5/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day5" +version = "0.1.0" diff --git a/5/Cargo.toml b/5/Cargo.toml new file mode 100644 index 0000000..49bdb49 --- /dev/null +++ b/5/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day5" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/5/src/main.rs b/5/src/main.rs new file mode 100644 index 0000000..9e64bf0 --- /dev/null +++ b/5/src/main.rs @@ -0,0 +1,230 @@ +use std::fmt::Debug; +use std::fs::File; +use std::io::{BufRead, BufReader, Lines}; +use std::time::{Duration, Instant}; + +// BOILERPLATE +type InputIter = Lines>; + +pub fn get_input() -> InputIter { + let f = File::open("input").unwrap(); + let br = BufReader::new(f); + br.lines() +} + +fn duration_format(duration: Duration) -> String { + match duration.as_secs_f64() { + x if x > 1.0 => format!("{:.3}s", x), + x if x > 0.010 => format!("{:.3}ms", x * 1e3), + x => format!("{:.3}us", x * 1e6), + } +} + +fn main() { + let input = get_input(); + let start = Instant::now(); + let ans1 = problem1(input); + let duration1 = start.elapsed(); + println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1)); + + let input = get_input(); + let start = Instant::now(); + let ans2 = problem2(input); + let duration2 = start.elapsed(); + println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2)); + println!("Total duration: {}", duration_format(duration1 + duration2)); +} + +trait Rule: Debug { + fn check(&self, pages: &Vec) -> bool; + fn fail_pos(&self, pages: &Vec) -> Option<(usize, usize)>; +} + +#[derive(Debug)] +struct BeforeRule { + a: u64, + b: u64, +} + +impl Rule for BeforeRule { + fn check(&self, pages: &Vec) -> bool { + let mut seen_a = false; + let mut seen_b = false; + for page in pages { + if *page == self.a { + if seen_b { + return false; + } + seen_a = true; + } else if *page == self.b { + if seen_a { + return true; + } + seen_b = true + } + } + return true; + } + fn fail_pos(&self, pages: &Vec) -> Option<(usize, usize)> { + let mut a_pos = None; + let mut b_pos = None; + for (pos, page) in pages.iter().enumerate() { + if *page == self.a { + if b_pos.is_some() { + return Some((b_pos.unwrap(), pos)); + } + a_pos = Some(pos); + } else if *page == self.b { + if a_pos.is_some() { + return None; + } + b_pos = Some(pos); + } + } + None + } +} + +impl From for BeforeRule { + fn from(line: String) -> BeforeRule { + let nums: Vec<_> = line.splitn(2, '|').map(|s| s.parse::().unwrap()).collect(); + BeforeRule { a: nums[0], b: nums[1] } + } +} + +#[derive(Debug)] +struct OrderingRules { + rules: Vec>, +} + +impl OrderingRules { + fn check(&self, pages: &Vec) -> bool { + self.rules.iter().all(|p| p.check(pages)) + } + fn fail_pos(&self, pages: &Vec) -> Option<(usize, usize)> { + for rule in &self.rules { + let fail_pos = rule.fail_pos(pages); + if fail_pos.is_some() { + return fail_pos + } + } + None + } +} + +// impl<'a, T: Iterator> From<&mut T> for OrderingRules { +// fn from(input: &mut T) -> Self { +// let mut rules = Vec::new(); +// for line in input { +// rules.push(line.into()) +// } +// Self { rules } +// } +// } + +// PROBLEM 1 solution + +fn problem1(mut input: Lines) -> u64 { + let rules = OrderingRules { + rules: input + .by_ref() + .map_while(|l| match l { + Ok(line) if line != "" => Some(Box::new(BeforeRule::from(line)) as _), + _ => None, + }) + .collect(), + }; + let updates: Vec> = input + .by_ref() + .map(|l| l.unwrap().split(',').map(|n| n.parse::().unwrap()).collect()) + .collect(); + + let mut res = 0; + for update in updates { + if rules.check(&update) { + res += update[update.len() / 2] + } + } + res +} + +// PROBLEM 2 solution +fn problem2(mut input: Lines) -> u64 { + let rules = OrderingRules { + rules: input + .by_ref() + .map_while(|l| match l { + Ok(line) if line != "" => Some(Box::new(BeforeRule::from(line)) as _), + _ => None, + }) + .collect(), + }; + let updates: Vec> = input + .by_ref() + .map(|l| l.unwrap().split(',').map(|n| n.parse::().unwrap()).collect()) + .collect(); + + let mut res = 0; + for mut update in updates { + let mut did_swaps = false; + while let Some((a,b)) = rules.fail_pos(&update) { + did_swaps = true; + update.swap(a, b); + } + if did_swaps { + if !rules.check(&update) { + panic!("update still fails after swaps") + } + + res += update[update.len() / 2]; + } + } + res +} + +#[cfg(test)] +mod tests { + use crate::*; + use std::io::Cursor; + + const EXAMPLE: &str = &"47|53 +97|13 +97|61 +97|47 +75|29 +61|13 +75|53 +29|13 +97|29 +53|29 +61|53 +97|53 +61|29 +47|13 +75|47 +97|75 +47|61 +75|61 +47|29 +75|13 +53|13 + +75,47,61,53,29 +97,61,53,29,13 +75,29,13 +75,97,47,61,53 +61,13,29 +97,13,75,29,47"; + + #[test] + fn problem1_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem1(c.lines()), 143); + } + + #[test] + fn problem2_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem2(c.lines()), 123); + } +}