diff --git a/2022/Cargo.toml b/2022/Cargo.toml index f35ce18..32c2cf2 100644 --- a/2022/Cargo.toml +++ b/2022/Cargo.toml @@ -39,6 +39,10 @@ path = "src/day7.rs" name = "day8" path = "src/day8.rs" +[[bin]] +name = "day9" +path = "src/day9.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/2022/src/day8.rs b/2022/src/day8.rs index 68eef8a..d887931 100644 --- a/2022/src/day8.rs +++ b/2022/src/day8.rs @@ -8,7 +8,7 @@ const DATA: &'static str = include_str!("../data/day8.txt"); // "; -// O(N * (2 * sqrt(N) - 1)), so roughly N^1.5? Where N is number of elements in the grid +// O(N * (2 * (sqrt(N) - 1))), so roughly N^1.5? Where N is number of elements in the grid fn is_visible(grid: &Vec>, x: usize, y: usize) -> bool { let height = grid[y][x]; let rows = grid.len(); diff --git a/2022/src/day9.rs b/2022/src/day9.rs new file mode 100644 index 0000000..6207864 --- /dev/null +++ b/2022/src/day9.rs @@ -0,0 +1,140 @@ +use std::collections::HashSet; +use std::str::FromStr; + + +#[derive(Copy, Clone, Debug)] +enum Direction { + Left, + Right, + Up, + Down, +} + +impl FromStr for Direction { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "L" => Ok(Direction::Left), + "R" => Ok(Direction::Right), + "U" => Ok(Direction::Up), + "D" => Ok(Direction::Down), + _ => Err(format!("Not a valid direction: {s}")), + } + } +} + + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +struct Pos { + x: isize, + y: isize, +} + + +#[derive(Debug)] +struct Rope { + knots: [Pos; L], + tail_visited: HashSet +} + +impl Rope { + fn new() -> Self { + Rope { + knots: [Pos {x: 0, y: 0}; L], + tail_visited: HashSet::new(), + } + } + + fn do_move(&mut self, dir: Direction, count: isize) { + for _ in 0..count { + let head = &mut self.knots[0]; + match dir { + Direction::Left => head.x -= 1, + Direction::Right => head.x += 1, + Direction::Up => head.y += 1, + Direction::Down => head.y -= 1, + } + + for i in 1..(self.knots.len()) { + self.move_follower(i); + } + + self.tail_visited.insert(*self.knots.last().unwrap()); + } + } + + fn move_follower(&mut self, i: usize) { + let leader = self.knots[i - 1]; + let follower = &mut self.knots[i]; + + let dx = leader.x - follower.x; + let dy = leader.y - follower.y; + + // check to make sure that we didn't do anything wrong previously + if dx.abs() + dy.abs() > 3 + || dx.abs() == 0 && dy.abs() == 3 + || dx.abs() == 3 && dy.abs() == 0 + { + panic!("Invalid head and tail positions: {dx}, {dy}"); + } + + // head and tail are in line vertically + if dx == 0 && dy.abs() == 2 { + follower.y += dy / 2; // use integer division to keep the sign of dy + } + // in line horizontally + if dy == 0 && dx.abs() == 2 { + follower.x += dx / 2; // similarly + } + + // head and tail are offset by 1 horizontaly and 2 vertically + if dx.abs() == 1 && dy.abs() == 2 { + follower.x += dx; + follower.y += dy / 2; + } + // offset by 1 vertically and 2 horizontally + if dy.abs() == 1 && dx.abs() == 2 { + follower.y += dy; + follower.x += dx / 2; + } + } +} + + +fn main() -> Result<(), String> { + let instructions = DATA.trim() + .lines() + .map(|line| { + let mut splat = line.split(' '); + match (splat.next(), splat.next()) { + (Some(dir), Some(dist)) => { + let dist = dist.parse().map_err(|_e| format!("Not an integer: {dist}")); + Ok((dir.parse()?, dist?)) + }, + _ => Err(format!("Could not parse move instruction from line: {line}")) + } + }) + .collect::, String>>()?; + + let mut rope = Rope::<10>::new(); + for (dir, dist) in instructions { + rope.do_move(dir, dist) + } + println!("{}", rope.tail_visited.len()); + + Ok(()) +} + + +// const DATA: &'static str = include_str!("../data/day9.txt"); +const DATA: &'static str = " +R 5 +U 8 +L 8 +D 3 +R 17 +D 10 +L 25 +U 20 +"; diff --git a/2022/src/lib.rs b/2022/src/lib.rs index e69de29..c3d7f2e 100644 --- a/2022/src/lib.rs +++ b/2022/src/lib.rs @@ -0,0 +1,67 @@ +#![allow(dead_code)] + +use std::ops::{Index, IndexMut}; + + +struct Grid { + rows: Vec> +} + + +impl Grid { + fn new() -> Self { + Grid { rows: vec![] } + } + + fn with_default_values(width: usize, height: usize) -> Self + where T: Default + Clone + { + let rows = (0..height) + .map(|_i| vec![Default::default(); width]) + .collect(); + + Grid { rows } + } + + fn from_rows(rows: I) -> Self + where I: Iterator> + { + Grid { rows: rows.collect() } + } + + fn from_chars(src: &str, conv: F) -> Result + where F: Fn(char) -> Result + { + let rows: Result>, E> = src.lines() + .map(|line| { + line.chars() + .map(|c| conv(c)) + .collect() + }) + .collect(); + + Ok(Grid { rows: rows? }) + } + + fn iter_rows(&self) -> impl Iterator> { + self.rows.iter() + } +} + + +impl Index<[usize; 2]> for Grid { + type Output = T; + + fn index(&self, idx: [usize; 2]) -> &T { + let [x, y] = idx; + &self.rows[y][x] + } +} + + +impl IndexMut<[usize; 2]> for Grid { + fn index_mut(&mut self, idx: [usize; 2]) -> &mut T { + let [x, y] = idx; + &mut self.rows[y][x] + } +}