diff --git a/README.md b/README.md index 8f1863ed..eb867043 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ Development occurs in language-specific directories: |[Day1.hs](hs/src/Day1.hs)|[Day1.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day1.kt)|[day1.py](py/aoc2024/day1.py)|[day1.rs](rs/src/day1.rs)| |[Day2.hs](hs/src/Day2.hs)|[Day2.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day2.kt)|[day2.py](py/aoc2024/day2.py)|[day2.rs](rs/src/day2.rs)| |[Day3.hs](hs/src/Day3.hs)|[Day3.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day3.kt)|[day3.py](py/aoc2024/day3.py)|[day3.rs](rs/src/day3.rs)| -|[Day4.hs](hs/src/Day4.hs)|[Day4.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day4.kt)|[day4.py](py/aoc2024/day4.py)|| +|[Day4.hs](hs/src/Day4.hs)|[Day4.kt](kt/aoc2024-lib/src/commonMain/kotlin/com/github/ephemient/aoc2024/Day4.kt)|[day4.py](py/aoc2024/day4.py)|[day4.rs](rs/src/day4.rs)| diff --git a/rs/benches/criterion.rs b/rs/benches/criterion.rs index 3a9e9b69..ee0c6c9c 100644 --- a/rs/benches/criterion.rs +++ b/rs/benches/criterion.rs @@ -1,4 +1,4 @@ -use aoc2024::{day1, day2, day3}; +use aoc2024::{day1, day2, day3, day4}; use criterion::{black_box, Criterion}; use std::env; use std::fs; @@ -32,6 +32,12 @@ fn aoc2024_bench(c: &mut Criterion) -> io::Result<()> { g.bench_function("part 2", |b| b.iter(|| day3::part2(black_box(&data)))); g.finish(); + let data = get_day_input(4)?; + let mut g = c.benchmark_group("day 4"); + g.bench_function("part 1", |b| b.iter(|| day4::part1(black_box(&data)))); + g.bench_function("part 2", |b| b.iter(|| day4::part2(black_box(&data)))); + g.finish(); + Ok(()) } diff --git a/rs/src/day4.rs b/rs/src/day4.rs new file mode 100644 index 00000000..e32620cf --- /dev/null +++ b/rs/src/day4.rs @@ -0,0 +1,78 @@ +use itertools::izip; + +pub fn part1(data: &str) -> usize { + let lines = data.lines().collect::>(); + let mut result = 0; + for (y, line) in lines.iter().enumerate() { + for x in 0..line.len() { + for dy in -1..=1 { + for dx in -1..=1 { + if "XMAS".bytes().enumerate().all(|(i, b)| { + y.checked_add_signed(i as isize * dy) + .and_then(|y| lines.get(y)) + .and_then(|line| { + x.checked_add_signed(i as isize * dx) + .and_then(|x| line.as_bytes().get(x)) + }) + == Some(&b) + }) { + result += 1; + } + } + } + } + } + result +} + +pub fn part2(data: &str) -> usize { + let lines = data.lines().collect::>(); + let mut result = 0; + for (above, line, below) in izip!(&lines[..], &lines[1..], &lines[2..]) { + for (nw, ne, b, sw, se) in izip!( + above.bytes(), + above.bytes().skip(2), + line.bytes().skip(1), + below.bytes(), + below.bytes().skip(2) + ) { + if b == b'A' + && (nw == b'M' && se == b'S' || se == b'M' && nw == b'S') + && (ne == b'M' && sw == b'S' || sw == b'M' && ne == b'S') + { + result += 1; + } + } + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + use pretty_assertions::assert_eq; + + static EXAMPLE: &str = indoc! {" + MMMSXXMASM + MSAMXMSMSA + AMXSXMAAMM + MSAMASMSMX + XMASAMXAMM + XXAMMXXAMA + SMSMSASXSS + SAXAMASAAA + MAMMMXMMMM + MXMXAXMASX + "}; + + #[test] + fn part1_examples() { + assert_eq!(18, part1(EXAMPLE)); + } + + #[test] + fn part2_examples() { + assert_eq!(9, part2(EXAMPLE)); + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 4e7d7123..5415e923 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -1,3 +1,4 @@ pub mod day1; pub mod day2; pub mod day3; +pub mod day4; diff --git a/rs/src/main.rs b/rs/src/main.rs index 2253ab6f..99a57c3b 100644 --- a/rs/src/main.rs +++ b/rs/src/main.rs @@ -1,4 +1,4 @@ -use aoc2024::{day1, day2, day3}; +use aoc2024::{day1, day2, day3, day4}; use std::collections::HashSet; use std::env; use std::fs; @@ -40,5 +40,13 @@ fn main() -> io::Result<()> { println!(); } + if args.is_empty() || args.contains("4") { + println!("Day 4"); + let data = get_day_input(4)?; + println!("{:?}", day4::part1(&data)); + println!("{:?}", day4::part2(&data)); + println!(); + } + Ok(()) }