diff --git a/2022/Cargo.lock b/2022/Cargo.lock index 2eaa899..66ba52a 100644 --- a/2022/Cargo.lock +++ b/2022/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "either", "itertools", "lazy_static", + "num", "parse-display", "regex", "rstest", @@ -164,6 +165,82 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.16.0" diff --git a/2022/Cargo.toml b/2022/Cargo.toml index 060c646..72cb360 100644 --- a/2022/Cargo.toml +++ b/2022/Cargo.toml @@ -10,6 +10,7 @@ regex = "1.6.0" parse-display = "0.7.0" serde_json = "1.0.89" either = "1.8.0" +num = "0.4.0" [dev-dependencies] rstest = "0.15.0" @@ -81,3 +82,6 @@ name = "puzzle_22" [[example]] name = "puzzle_23" + +[[example]] +name = "puzzle_24" diff --git a/2022/examples/puzzle_23/main.rs b/2022/examples/puzzle_23/main.rs index d982931..afd9a26 100644 --- a/2022/examples/puzzle_23/main.rs +++ b/2022/examples/puzzle_23/main.rs @@ -1,12 +1,7 @@ +use num::complex::Complex; use std::collections::{HashMap, HashSet}; use std::time::Instant; -#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] -struct Point { - x: i64, - y: i64, -} - #[derive(Debug, Clone, Eq, PartialEq, Hash)] enum Direction { NORTH, @@ -19,210 +14,96 @@ enum Direction { SOUTH_WEST, } -impl Point { - fn neighbours(&self) -> HashMap { - HashMap::from_iter(vec![ - ( - Direction::EAST, - Point { - x: self.x + 1, - y: self.y, - }, - ), - ( - Direction::WEST, - Point { - x: self.x - 1, - y: self.y, - }, - ), - ( - Direction::NORTH, - Point { - x: self.x, - y: self.y - 1, - }, - ), - ( - Direction::SOUTH, - Point { - x: self.x, - y: self.y + 1, - }, - ), - ( - Direction::SOUTH_EAST, - Point { - x: self.x + 1, - y: self.y + 1, - }, - ), - ( - Direction::NORTH_EAST, - Point { - x: self.x + 1, - y: self.y - 1, - }, - ), - ( - Direction::NORTH_WEST, - Point { - x: self.x - 1, - y: self.y - 1, - }, - ), - ( - Direction::SOUTH_WEST, - Point { - x: self.x - 1, - y: self.y + 1, - }, - ), - ]) - } +fn neighbours(point: &Complex) -> HashMap> { + HashMap::from_iter(vec![ + (Direction::EAST, point + Complex::new(1, 0)), + (Direction::WEST, point + Complex::new(-1, 0)), + (Direction::NORTH, point + Complex::new(0, -1)), + (Direction::SOUTH, point + Complex::new(0, 1)), + (Direction::SOUTH_EAST, point + Complex::new(1, 1)), + (Direction::NORTH_EAST, point + Complex::new(1, -1)), + (Direction::NORTH_WEST, point + Complex::new(-1, -1)), + (Direction::SOUTH_WEST, point + Complex::new(-1, 1)), + ]) +} - fn check_north( - &self, - has_neighbours: &HashMap, - round: usize, - ) -> (Option, usize) { - // If there is no Elf in the N, NE, or NW adjacent positions, the Elf proposes moving north one step. - if !has_neighbours.get(&Direction::NORTH).unwrap() - && !has_neighbours.get(&Direction::NORTH_EAST).unwrap() - && !has_neighbours.get(&Direction::NORTH_WEST).unwrap() - { - let priority = if round % 4 == 0 { - 0 - } else if round % 4 == 1 { - 3 - } else if round % 4 == 2 { - 2 - } else { - 1 - }; - ( - Some(Point { - x: self.x, - y: self.y - 1, - }), - priority, - ) - } else { - (None, usize::MAX) - } +fn check_north( + point: &Complex, + has_neighbours: &HashMap, +) -> Option> { + // If there is no Elf in the N, NE, or NW adjacent positions, the Elf proposes moving north one step. + if !has_neighbours.get(&Direction::NORTH).unwrap() + && !has_neighbours.get(&Direction::NORTH_EAST).unwrap() + && !has_neighbours.get(&Direction::NORTH_WEST).unwrap() + { + Some(point + Complex::new(0, -1)) + } else { + None } +} - fn check_south( - &self, - has_neighbours: &HashMap, - round: usize, - ) -> (Option, usize) { - // If there is no Elf in the S, SE, or SW adjacent positions, the Elf proposes moving south one step. - if !has_neighbours.get(&Direction::SOUTH).unwrap() - && !has_neighbours.get(&Direction::SOUTH_EAST).unwrap() - && !has_neighbours.get(&Direction::SOUTH_WEST).unwrap() - { - let priority = if round % 4 == 0 { - 1 - } else if round % 4 == 1 { - 0 - } else if round % 4 == 2 { - 3 - } else { - 2 - }; - ( - Some(Point { - x: self.x, - y: self.y + 1, - }), - priority, - ) - } else { - (None, usize::MAX) - } +fn check_south( + point: &Complex, + has_neighbours: &HashMap, +) -> Option> { + // If there is no Elf in the S, SE, or SW adjacent positions, the Elf proposes moving south one step. + if !has_neighbours.get(&Direction::SOUTH).unwrap() + && !has_neighbours.get(&Direction::SOUTH_EAST).unwrap() + && !has_neighbours.get(&Direction::SOUTH_WEST).unwrap() + { + Some(point + Complex::new(0, 1)) + } else { + None } +} - fn check_east( - &self, - has_neighbours: &HashMap, - round: usize, - ) -> (Option, usize) { - // If there is no Elf in the E, NE, or SE adjacent positions, the Elf proposes moving east one step. - if !has_neighbours.get(&Direction::EAST).unwrap() - && !has_neighbours.get(&Direction::NORTH_EAST).unwrap() - && !has_neighbours.get(&Direction::SOUTH_EAST).unwrap() - { - let priority = if round % 4 == 0 { - 3 - } else if round % 4 == 1 { - 2 - } else if round % 4 == 2 { - 1 - } else { - 0 - }; - ( - Some(Point { - x: self.x + 1, - y: self.y, - }), - priority, - ) - } else { - (None, usize::MAX) - } +fn check_east( + point: &Complex, + has_neighbours: &HashMap, +) -> Option> { + // If there is no Elf in the E, NE, or SE adjacent positions, the Elf proposes moving east one step. + if !has_neighbours.get(&Direction::EAST).unwrap() + && !has_neighbours.get(&Direction::NORTH_EAST).unwrap() + && !has_neighbours.get(&Direction::SOUTH_EAST).unwrap() + { + Some(point + Complex::new(1, 0)) + } else { + None } +} - fn check_west( - &self, - has_neighbours: &HashMap, - round: usize, - ) -> (Option, usize) { - // If there is no Elf in the W, NW, or SW adjacent positions, the Elf proposes moving west one step. - if !has_neighbours.get(&Direction::WEST).unwrap() - && !has_neighbours.get(&Direction::SOUTH_WEST).unwrap() - && !has_neighbours.get(&Direction::NORTH_WEST).unwrap() - { - let priority = if round % 4 == 0 { - 2 - } else if round % 4 == 1 { - 1 - } else if round % 4 == 2 { - 0 - } else { - 3 - }; - ( - Some(Point { - x: self.x - 1, - y: self.y, - }), - priority, - ) - } else { - (None, usize::MAX) - } +fn check_west( + point: &Complex, + has_neighbours: &HashMap, +) -> Option> { + // If there is no Elf in the W, NW, or SW adjacent positions, the Elf proposes moving west one step. + if !has_neighbours.get(&Direction::WEST).unwrap() + && !has_neighbours.get(&Direction::SOUTH_WEST).unwrap() + && !has_neighbours.get(&Direction::NORTH_WEST).unwrap() + { + Some(point + Complex::new(-1, 0)) + } else { + None } } -fn get_answer(grid: HashSet) -> i64 { - let min_x = grid.iter().map(|p| p.x).min().unwrap(); - let max_x = grid.iter().map(|p| p.x).max().unwrap(); - let min_y = grid.iter().map(|p| p.y).min().unwrap(); - let max_y = grid.iter().map(|p| p.y).max().unwrap(); +fn get_answer(grid: HashSet>) -> i64 { + let min_x = grid.iter().map(|p| p.re).min().unwrap(); + let max_x = grid.iter().map(|p| p.re).max().unwrap(); + let min_y = grid.iter().map(|p| p.im).min().unwrap(); + let max_y = grid.iter().map(|p| p.im).max().unwrap(); (max_x - min_x + 1) * (max_y - min_y + 1) - (grid.len() as i64) } -fn print_grid(grid: &HashSet) { - let min_x = grid.iter().map(|p| p.x).min().unwrap(); - let max_x = grid.iter().map(|p| p.x).max().unwrap(); - let min_y = grid.iter().map(|p| p.y).min().unwrap(); - let max_y = grid.iter().map(|p| p.y).max().unwrap(); +fn print_grid(grid: &HashSet>) { + let min_x = grid.iter().map(|p| p.re).min().unwrap(); + let max_x = grid.iter().map(|p| p.re).max().unwrap(); + let min_y = grid.iter().map(|p| p.im).min().unwrap(); + let max_y = grid.iter().map(|p| p.im).max().unwrap(); let mut grid_vec: Vec> = vec![vec!['.'; (max_x - min_x + 1) as usize]; (max_y - min_y + 1) as usize]; for point in grid.iter() { - grid_vec[(point.y - min_y) as usize][(point.x - min_x) as usize] = '#' + grid_vec[(point.im - min_y) as usize][(point.re - min_x) as usize] = '#' } for row in grid_vec { for char in row { @@ -232,42 +113,49 @@ fn print_grid(grid: &HashSet) { } } -fn simulate(contents: &str, num_rounds: usize) -> (HashSet, usize) { - let mut grid: HashSet = HashSet::new(); +fn simulate(contents: &str, num_rounds: usize) -> (HashSet>, usize) { + let mut grid: HashSet> = HashSet::new(); for (y, line) in contents.lines().enumerate() { for (x, char) in line.chars().enumerate() { if char == '#' { - grid.insert(Point { - x: x as i64, - y: y as i64, - }); + grid.insert(Complex::new(x as i64, y as i64)); } } } + let num_elves = grid.len(); let mut rounds_completed = 0; for round in 0..num_rounds { - let mut new_grid: HashSet = HashSet::new(); - let mut new_point_counts: HashMap = HashMap::new(); - let mut potential_moves: HashMap = HashMap::new(); + let mut new_grid: HashSet> = HashSet::with_capacity(num_elves); + let mut new_point_counts: HashMap, usize> = HashMap::with_capacity(num_elves); + let mut potential_moves: HashMap, Complex> = + HashMap::with_capacity(num_elves); + let mut no_moves = 0; for point in grid.iter() { - let neighbours = point.neighbours(); + let neighbours = neighbours(&point); let has_neighbours: HashMap = HashMap::from_iter(neighbours.into_iter().map(|(d, p)| (d, grid.contains(&p)))); let possible = if has_neighbours.iter().all(|(_, x)| !x) { - Some((Some(point.clone()), 0)) + None } else { - let mut possibles: Vec<(Option, usize)> = Vec::new(); - possibles.push(point.check_north(&has_neighbours, round)); - possibles.push(point.check_south(&has_neighbours, round)); - possibles.push(point.check_west(&has_neighbours, round)); - possibles.push(point.check_east(&has_neighbours, round)); - possibles.sort_by(|a, b| b.1.cmp(&a.1)); - possibles.pop() + let possibles: Vec>> = vec![ + check_north(&point, &has_neighbours), + check_south(&point, &has_neighbours), + check_west(&point, &has_neighbours), + check_east(&point, &has_neighbours), + ]; + + possibles + .into_iter() + .cycle() + .skip(round % 4) + .take(4) + .flatten() + .next() }; match possible { - Some((Some(new_point), _)) => { + Some(new_point) => { new_point_counts .entry(new_point) .and_modify(|x| *x += 1) @@ -275,22 +163,17 @@ fn simulate(contents: &str, num_rounds: usize) -> (HashSet, usize) { potential_moves.insert(point.clone(), new_point.clone()); } _ => { - potential_moves.insert(point.clone(), point.clone()); + new_grid.insert(*point); + no_moves += 1; } } } - let mut no_moves = 0; for (point, other_point) in potential_moves { - if point == other_point { - // no move case - new_grid.insert(point); - no_moves += 1; - continue; - } match new_point_counts.get(&other_point) { Some(c) if c > &1 => { new_grid.insert(point); + no_moves += 1; } Some(_) => { new_grid.insert(other_point); @@ -299,7 +182,7 @@ fn simulate(contents: &str, num_rounds: usize) -> (HashSet, usize) { } } rounds_completed += 1; - if no_moves == new_grid.len() { + if no_moves == num_elves { break; } grid = new_grid; diff --git a/2022/examples/puzzle_24/main.rs b/2022/examples/puzzle_24/main.rs new file mode 100644 index 0000000..3fdf722 --- /dev/null +++ b/2022/examples/puzzle_24/main.rs @@ -0,0 +1,43 @@ +use std::time::Instant; + +fn part_1(contents: &str) -> u64 { + 0 +} + +fn part_2(contents: &str) -> u64 { + 0 +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::*; + + #[rstest] + #[case("", 1)] + fn test_(#[case] input: &str, #[case] expected: u64) { + assert_eq!(part_1(input), expected); + } + + #[test] + fn test_part_1_example() { + assert_eq!(part_1(include_str!("./example.txt")), 1); + } + + #[test] + fn test_part_2_example() { + assert_eq!(part_2(include_str!("./example.txt")), 1); + } + +} + +fn main() { + let start = Instant::now(); + let contents = include_str!("./input.txt"); + let part_1_answer = part_1(contents); + println!("Answer for part 1 is: {}", part_1_answer); + let part_2_answer = part_2(contents); + println!("Answer for part 2 is: {}", part_2_answer); + let duration = start.elapsed(); + println!("Took {:?} to solve this puzzle", duration); +}