From a81904f2eb2c95f504eaef349e3efba06bd8dff6 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Tue, 3 Dec 2024 22:02:49 -0800 Subject: [PATCH] day4: complete solution --- 4/Cargo.lock | 7 ++ 4/Cargo.toml | 6 ++ 4/src/main.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 4/Cargo.lock create mode 100644 4/Cargo.toml create mode 100644 4/src/main.rs diff --git a/4/Cargo.lock b/4/Cargo.lock new file mode 100644 index 0000000..e92c02c --- /dev/null +++ b/4/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day4" +version = "0.1.0" diff --git a/4/Cargo.toml b/4/Cargo.toml new file mode 100644 index 0000000..0ebbbc8 --- /dev/null +++ b/4/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day4" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/4/src/main.rs b/4/src/main.rs new file mode 100644 index 0000000..dc57e5b --- /dev/null +++ b/4/src/main.rs @@ -0,0 +1,188 @@ +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)); +} + +struct WordSearch { + rows: Vec, +} + +impl From> for WordSearch { + fn from(input: Lines) -> Self { + let mut rows = Vec::new(); + for line in input.map(|i| i.unwrap()) { + rows.push(line); + } + Self { rows } + } +} + +impl WordSearch { + fn count_occurences(haystack: &str, needle: &str) -> u64 { + let mut count = 0; + for start in 0..haystack.len() - needle.len() + 1 { + if &haystack[start..start + needle.len()] == needle { + count += 1; + } + } + count + } + fn count_forward(&self, needle: &str) -> u64 { + let mut count = 0; + for row in &self.rows { + count += Self::count_occurences(row, needle) + } + count + } + fn count_vertical(&self, needle: &str) -> u64 { + let mut count = 0; + for col in 0..self.rows[0].len() { + let s: String = self.rows.iter().map(|row| row.chars().nth(col).unwrap()).collect(); + count += Self::count_occurences(&s, needle) + } + count + } + fn count_diagonal(&self, needle: &str) -> u64 { + let width = self.rows[0].len(); + let height = self.rows.len(); + + let mut count = 0; + for x in 0..width { + for y in 0..height { + // check down-right + if x <= width - needle.len() && y <= height - needle.len() { + let s: String = (0..needle.len()) + .into_iter() + .map(|i| self.rows[y + i].chars().nth(x + i).unwrap()) + .collect(); + if s == needle { + count += 1 + } + } + // check down-left + if x >= needle.len() - 1 && y <= height - needle.len() { + let s: String = (0..needle.len()) + .into_iter() + .map(|i| self.rows[y + i].chars().nth(x - i).unwrap()) + .collect(); + if s == needle { + count += 1 + } + } + } + } + count + } + + fn get(&self, x: usize, y: usize) -> char { + self.rows[y].chars().nth(x).unwrap() + } + + fn count_x_mas(&self) -> u64 { + // M.M M.S S.M S.S + // .A. .A. .A. .A. + // S.S M.S S.M M.M + let searches: [[char;5]; 4] = ["MMASS", "MSAMS", "SMASM", "SSAMM"].map(|s| s.chars().collect::>().try_into().unwrap()); + let width = self.rows[0].len(); + let height = self.rows.len(); + + let mut count = 0; + for x in 0..width - 2 { + for y in 0..height - 2 { + let s = [ + self.get(x, y), + self.get(x + 2, y), + self.get(x + 1, y + 1), + self.get(x, y + 2), + self.get(x + 2, y + 2), + ]; + for needle in &searches { + if needle == &s { + count += 1 + } + } + } + } + count + } +} + +// PROBLEM 1 solution + +fn problem1(input: Lines) -> u64 { + let needle = "XMAS"; + let needle_rev: String = needle.chars().rev().collect(); + let ws = WordSearch::from(input); + ws.count_forward(needle) + + ws.count_forward(&needle_rev) + + ws.count_vertical(needle) + + ws.count_vertical(&needle_rev) + + ws.count_diagonal(needle) + + ws.count_diagonal(&needle_rev) +} + +// PROBLEM 2 solution +fn problem2(input: Lines) -> u64 { + let ws = WordSearch::from(input); + ws.count_x_mas() +} + +#[cfg(test)] +mod tests { + use crate::*; + use std::io::Cursor; + + const EXAMPLE: &str = &"MMMSXXMASM +MSAMXMSMSA +AMXSXMAAMM +MSAMASMSMX +XMASAMXAMM +XXAMMXXAMA +SMSMSASXSS +SAXAMASAAA +MAMMMXMMMM +MXMXAXMASX"; + + #[test] + fn problem1_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem1(c.lines()), 18); + } + + #[test] + fn problem2_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem2(c.lines()), 9); + } +}