diff --git a/8/Cargo.lock b/8/Cargo.lock new file mode 100644 index 0000000..46525ab --- /dev/null +++ b/8/Cargo.lock @@ -0,0 +1,30 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day8" +version = "0.1.0" +dependencies = [ + "grid", + "itertools", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "grid" +version = "0.1.0" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] diff --git a/8/Cargo.toml b/8/Cargo.toml new file mode 100644 index 0000000..2e9e497 --- /dev/null +++ b/8/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day8" +version = "0.1.0" +edition = "2021" + +[dependencies] +grid = { version = "0.1.0", path = "../libs/grid" } +itertools = "0.13.0" diff --git a/8/src/main.rs b/8/src/main.rs new file mode 100644 index 0000000..69c2aa0 --- /dev/null +++ b/8/src/main.rs @@ -0,0 +1,148 @@ +use grid::Grid; +use itertools::Itertools; +use std::collections::HashSet; +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 AntennaMap { + map: Grid, + antinodes: Grid, +} + +impl From> for AntennaMap { + fn from(input: Lines) -> Self { + let map = Grid::from(input); + let width = map.width(); + let height = map.height(); + Self { + map, + antinodes: Grid::with_shape(width, height, b'.'), + } + } +} + +// PROBLEM 1 solution + +fn problem1(input: Lines) -> u64 { + let mut map = AntennaMap::from(input); + + // find the unique frequencies in a dumb way + let freq_set: HashSet<&u8> = HashSet::from_iter(map.map.data.iter().filter(|c| **c != b'.')); + + // for each unique frequency, get all the pairs' positions + for freq in freq_set { + let coords = map + .map + .data + .iter() + .enumerate() + .filter(|(_, c)| *c == freq) + .map(|(i, _)| map.map.coord(i as i64).unwrap()) + .collect_vec(); + + for pair in coords.iter().permutations(2).collect_vec() { + let (a, b) = (pair[0], pair[1]); + let node_pos = (a.0 + a.0 - b.0, a.1 + a.1 - b.1); + map.antinodes.set(node_pos.0, node_pos.1, b'#'); + } + } + map.antinodes.count(b'#') as u64 +} + +// PROBLEM 2 solution +fn problem2(input: Lines) -> u64 { + let mut map = AntennaMap::from(input); + + // find the unique frequencies in a dumb way + let freq_set: HashSet<&u8> = HashSet::from_iter(map.map.data.iter().filter(|c| **c != b'.')); + + // for each unique frequency, get all the pairs' positions + for freq in freq_set { + let coords = map + .map + .data + .iter() + .enumerate() + .filter(|(_, c)| *c == freq) + .map(|(i, _)| map.map.coord(i as i64).unwrap()) + .collect_vec(); + + for pair in coords.iter().permutations(2).collect_vec() { + let (a, b) = (pair[0], pair[1]); + let offset = (a.0 - b.0, a.1 - b.1); + let mut i = 0; + loop { + let node_pos = (a.0 + i * offset.0, a.1 + i * offset.1); + if !map.antinodes.set(node_pos.0, node_pos.1, b'#') { + break; + } + i += 1; + } + } + } + map.antinodes.count(b'#') as u64 +} + +#[cfg(test)] +mod tests { + use crate::*; + use std::io::Cursor; + + const EXAMPLE: &str = &"............ +........0... +.....0...... +.......0.... +....0....... +......A..... +............ +............ +........A... +.........A.. +............ +............"; + + #[test] + fn problem1_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem1(c.lines()), 14); + } + + #[test] + fn problem2_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem2(c.lines()), 34); + } +} diff --git a/libs/grid/src/lib.rs b/libs/grid/src/lib.rs index d46a378..5c61ddc 100644 --- a/libs/grid/src/lib.rs +++ b/libs/grid/src/lib.rs @@ -1,6 +1,7 @@ use std::{ fmt::{self, Debug, Display, Formatter, Write}, io::{BufRead, Lines}, + iter::repeat, }; #[derive(Clone)] @@ -16,6 +17,12 @@ impl Grid { width, } } + pub fn with_shape(width: usize, height: usize, fill: T) -> Self { + Self { + data: Vec::from_iter(repeat(fill).take(width * height)), + width: width as i64, + } + } pub fn width(&self) -> usize { return self.width as usize; } @@ -25,7 +32,7 @@ impl Grid { fn pos(&self, x: i64, y: i64) -> i64 { y * self.width + x } - fn coord(&self, pos: i64) -> Option<(i64, i64)> { + pub fn coord(&self, pos: i64) -> Option<(i64, i64)> { if pos < 0 || pos >= self.data.len() as i64 { None } else {