Skip to content

Commit

Permalink
Day 4
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhurt committed Dec 4, 2024
1 parent 4b9db96 commit 1defde9
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 23 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.

| Day | Part 1 | Part 2 |
| :---: | :---: | :---: |
| [Day 1](./src/bin/01.rs) | `36.4µs` | `48.9µs` |
| [Day 2](./src/bin/02.rs) | `157.0µs` | `200.7µs` |
| [Day 3](./src/bin/03.rs) | `31.8µs` | `33.2µs` |
| [Day 1](./src/bin/01.rs) | `36.8µs` | `48.6µs` |
| [Day 2](./src/bin/02.rs) | `159.2µs` | `200.8µs` |
| [Day 3](./src/bin/03.rs) | `31.1µs` | `33.2µs` |
| [Day 4](./src/bin/04.rs) | `1.3ms` | `876.4µs` |

**Total: 0.51ms**
**Total: 2.69ms**
<!--- benchmarking table --->

---
Expand Down
10 changes: 10 additions & 0 deletions data/examples/04.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX
80 changes: 80 additions & 0 deletions src/bin/04.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::collections::HashSet;

use advent_of_code::{FullCompass, Grid};
use itertools::Itertools;
use strum::IntoEnumIterator;

advent_of_code::solution!(4);

/// Compare an iterator of characters to a string slice
fn iter_eq<I>(left: I, right: &str) -> bool
where
I: Iterator<Item = char>,
{
left.take(right.len())
.zip_longest(right.chars())
.map(|e| e.left_and_right())
.all(|(l, r)| l == r)
}

pub fn part_one(input: &str) -> Option<usize> {
let grid: Grid<char> = Grid::parse_lines(input);

let result = (0..grid.data.len())
.cartesian_product(FullCompass::iter())
.map(|(start, dir)| grid.ray(start, dir))
.map(|ray| iter_eq(ray.copied(), "XMAS"))
.filter(|v| *v)
.count();

Some(result)
}

pub fn part_two(input: &str) -> Option<usize> {
use FullCompass as D;

let mut back_as = HashSet::new();
let mut forwards_as = HashSet::new();

let grid: Grid<char> = Grid::parse_lines(input);

(0..grid.data.len())
.cartesian_product([D::NE, D::NW, D::SE, D::SW])
.map(|(start, dir)| (start, dir, grid.ray(start, dir)))
.map(|(start, dir, ray)| (start, dir, iter_eq(ray.copied(), "MAS")))
.filter(|(_, _, keep)| *keep)
.map(|(start, dir, _)| {
(
matches!(dir, D::NW | D::SE),
grid.step_from_index(start, dir).unwrap(),
)
})
.for_each(|(back, a)| {
if back {
back_as.insert(a);
} else {
forwards_as.insert(a);
}
});

Some(back_as.intersection(&forwards_as).count())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_part_one() {
let result =
part_one(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(18));
}

#[test]
fn test_part_two() {
let result =
part_two(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(9));
}
}
90 changes: 78 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,30 @@ pub enum Compass {
W,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
pub enum FullCompass {
#[default]
N,
NE,
E,
SE,
S,
SW,
W,
NW,
}

impl From<Compass> for FullCompass {
fn from(value: Compass) -> Self {
match value {
Compass::N => FullCompass::N,
Compass::E => FullCompass::E,
Compass::S => FullCompass::S,
Compass::W => FullCompass::W,
}
}
}

impl Compass {
pub fn from_relative(relative: char) -> Option<Self> {
match relative {
Expand Down Expand Up @@ -163,15 +187,30 @@ impl<T> Grid<T> {
}

#[allow(clippy::unnecessary_lazy_evaluations)]
pub fn step_from_index(&self, i: usize, dir: Compass) -> Option<usize> {
use Compass as D;
pub fn step_from_index<D>(&self, i: usize, dir: D) -> Option<usize>
where
FullCompass: From<D>,
{
use FullCompass as D;

let (col, row) = self.to_col_row(i);
let max_row = self.height - 1;
let max_col = self.width - 1;

let res_col_row = match FullCompass::from(dir) {
D::N => (row > 0).then(|| (col, row - 1)),
D::NE => (row > 0 && col < max_col).then(|| (col + 1, row - 1)),
D::E => (col < max_col).then(|| (col + 1, row)),
D::SE => {
(row < max_row && col < max_col).then(|| (col + 1, row + 1))
}
D::S => (row < max_row).then(|| (col, row + 1)),
D::SW => (row < max_row && col > 0).then(|| (col - 1, row + 1)),
D::W => (col > 0).then(|| (col - 1, row)),
D::NW => (row > 0 && col > 0).then(|| (col - 1, row - 1)),
};

match dir {
D::E => (i % self.width < (self.width - 1)).then_some(i + 1),
D::W => (i % self.width > 0).then(|| i - 1),
D::N => i.checked_sub(self.width),
D::S => Some(i + self.width).filter(|j| *j < self.data.len()),
}
res_col_row.map(|(col, row)| self.to_index(col, row))
}

pub fn neighbors(
Expand Down Expand Up @@ -226,12 +265,39 @@ impl<T> Grid<T> {
.into_iter()
}

pub fn to_col_row(&self, i: usize) -> (i32, i32) {
((i % self.width) as i32, (i / self.width) as i32)
pub fn to_col_row(&self, i: usize) -> (usize, usize) {
(i % self.width, i / self.width)
}

pub fn to_index(&self, col: usize, row: usize) -> usize {
col + row * self.width
}

pub fn ray(&self, index: usize, dir: FullCompass) -> RayIter<'_, T> {
RayIter {
grid: self,
dir,
curr: Some(index),
}
}
}

pub struct RayIter<'a, T> {
grid: &'a Grid<T>,
dir: FullCompass,
curr: Option<usize>,
}

impl<'a, T> Iterator for RayIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let result = self.curr;

let curr = result?;

self.curr = self.grid.step_from_index(curr, self.dir);

pub fn to_index(&self, col: i32, row: i32) -> usize {
col as usize + row as usize * self.width
result.map(|i| &self.grid.data[i])
}
}

Expand Down
34 changes: 27 additions & 7 deletions src/template/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ use std::{cmp, env, process};

use super::ANSI_BOLD;

pub fn run_part<I: Clone, T: Display>(func: impl Fn(I) -> Option<T>, input: I, day: Day, part: u8) {
pub fn run_part<I: Clone, T: Display>(
func: impl Fn(I) -> Option<T>,
input: I,
day: Day,
part: u8,
) {
let part_str = format!("Part {part}");

let (result, duration, samples) =
Expand Down Expand Up @@ -45,7 +50,11 @@ fn run_timed<I: Clone, T>(
(result, run.0, run.1)
}

fn bench<I: Clone, T>(func: impl Fn(I) -> T, input: I, base_time: &Duration) -> (Duration, u128) {
fn bench<I: Clone, T>(
func: impl Fn(I) -> T,
input: I,
base_time: &Duration,
) -> (Duration, u128) {
let mut stdout = stdout();

print!(" > {ANSI_ITALIC}benching{ANSI_RESET}");
Expand All @@ -54,7 +63,8 @@ fn bench<I: Clone, T>(func: impl Fn(I) -> T, input: I, base_time: &Duration) ->
let bench_iterations = cmp::min(
10000,
cmp::max(
Duration::from_secs(1).as_nanos() / cmp::max(base_time.as_nanos(), 10),
Duration::from_secs(1).as_nanos()
/ cmp::max(base_time.as_nanos(), 10),
10,
),
);
Expand Down Expand Up @@ -92,7 +102,11 @@ fn format_duration(duration: &Duration, samples: u128) -> String {
}
}

fn print_result<T: Display>(result: &Option<T>, part: &str, duration_str: &str) {
fn print_result<T: Display>(
result: &Option<T>,
part: &str,
duration_str: &str,
) {
let is_intermediate_result = duration_str.is_empty();

match result {
Expand All @@ -107,7 +121,9 @@ fn print_result<T: Display>(result: &Option<T>, part: &str, duration_str: &str)
println!("{result}");
}
} else {
let str = format!("{part}: {ANSI_BOLD}{result}{ANSI_RESET}{duration_str}");
let str = format!(
"{part}: {ANSI_BOLD}{result}{ANSI_RESET}{duration_str}"
);
if is_intermediate_result {
print!("{str}");
} else {
Expand Down Expand Up @@ -142,14 +158,18 @@ fn submit_result<T: Display>(
}

if args.len() < 3 {
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
eprintln!(
"Unexpected command-line input. Format: cargo solve 1 --submit 1"
);
process::exit(1);
}

let part_index = args.iter().position(|x| x == "--submit").unwrap() + 1;

let Ok(part_submit) = args[part_index].parse::<u8>() else {
eprintln!("Unexpected command-line input. Format: cargo solve 1 --submit 1");
eprintln!(
"Unexpected command-line input. Format: cargo solve 1 --submit 1"
);
process::exit(1);
};

Expand Down

0 comments on commit 1defde9

Please sign in to comment.