From ea7cf29946b89fd52d4ae053b06c17dfe4b5f8db Mon Sep 17 00:00:00 2001 From: Juha Komulainen Date: Fri, 17 Nov 2023 23:02:16 +0200 Subject: [PATCH] Optimization --- README.md | 2 +- src/bin/19.rs | 24 ++++++--- src/bin/23.rs | 99 ++++++++++++++++++++++++++++------- src/bin/24.rs | 120 +++++++++++++++++++++++++++++++------------ src/bin/25.rs | 16 ++++-- src/point.rs | 7 ++- src/shortest_path.rs | 4 +- 7 files changed, 206 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 0a48985..153118d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ All rows with solutions over a millisecond are marked with 😔. | [21](https://adventofcode.com/2022/day/21) | [21.rs](src/bin/21.rs) | 325.30µs | 235.40µs | - | | [22](https://adventofcode.com/2022/day/22) | [22.rs](src/bin/22.rs) | 139.31µs | 127.73µs | - | | [23](https://adventofcode.com/2022/day/23) | [23.rs](src/bin/23.rs) | 15.51ms | 831.17ms | 😔 | -| [24](https://adventofcode.com/2022/day/24) | [24.rs](src/bin/24.rs) | 4.21s | 39.31s | 😔 | +| [24](https://adventofcode.com/2022/day/24) | [24.rs](src/bin/24.rs) | 80.10ms | 1.16s | 😔 | | [25](https://adventofcode.com/2022/day/25) | [25.rs](src/bin/25.rs) | 17.04µs | - | - | In the end, days 15, 19, 23 and 24 blew the 100 ms budget by themselves, but ignoring those the total time for the rest diff --git a/src/bin/19.rs b/src/bin/19.rs index a9c3837..382c63d 100644 --- a/src/bin/19.rs +++ b/src/bin/19.rs @@ -10,13 +10,23 @@ use regex::Regex; pub fn part_one(input: &str) -> Option { let blueprints = parse_lines::(input).collect::>(); - Some(blueprints.par_iter().map(|b| b.id as u16 * b.max_geodes(24) as u16).sum()) + Some( + blueprints + .par_iter() + .map(|b| b.id as u16 * b.max_geodes(24) as u16) + .sum(), + ) } pub fn part_two(input: &str) -> Option { let blueprints = parse_lines::(input).take(3).collect::>(); - Some(blueprints.par_iter().map(|b| b.max_geodes(32) as u16).product()) + Some( + blueprints + .par_iter() + .map(|b| b.max_geodes(32) as u16) + .product(), + ) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -80,10 +90,11 @@ impl SearchState { 5..=5 => 2, 6..=9 => 3, 10..=13 => 4, - _ => 5 + _ => 5, }; - self.geodes + self.remaining_minutes as GeodeCount * (self.geode_robots as GeodeCount + extra) + self.geodes + + self.remaining_minutes as GeodeCount * (self.geode_robots as GeodeCount + extra) } fn collect_materials(&mut self) { @@ -145,7 +156,6 @@ impl Blueprint { cache: &mut HashMap, best: &mut GeodeCount, ) -> GeodeCount { - let cacheable = state.remaining_minutes < 25; if state.remaining_minutes == 0 { if state.geodes > *best { @@ -153,7 +163,7 @@ impl Blueprint { } return state.geodes; } else if state.geode_estimate() < *best { - return 0 + return 0; } else if cacheable { if let Some(result) = cache.get(&state) { return *result; @@ -174,14 +184,12 @@ impl Blueprint { if can_build_geode_robot { state.build_robot(Material::Geode, self); result = result.max(self.recurse(state, cache, best)); - } else if can_build_obsidian_robot { let without_robot = state.clone(); state.build_robot(Material::Obsidian, self); result = result.max(self.recurse(state, cache, best)); result = result.max(self.recurse(without_robot, cache, best)); - } else { if can_build_clay_robot { let mut new_state = state.clone(); diff --git a/src/bin/23.rs b/src/bin/23.rs index 1503fd3..ef407ca 100644 --- a/src/bin/23.rs +++ b/src/bin/23.rs @@ -1,7 +1,7 @@ +use aoc::point::{CardinalDirection, CompassDirection}; use enum_iterator::all; use hashbrown::{HashMap, HashSet}; use itertools::Itertools; -use aoc::point::{CardinalDirection, CompassDirection}; pub fn part_one(input: &str) -> Option { Some(run(input, 10, false)) @@ -15,10 +15,30 @@ pub fn run(input: &str, rounds: u32, round_count: bool) -> u32 { let mut elves = ElfMap::parse(input); let directions_vecs = vec![ - vec![CardinalDirection::N, CardinalDirection::S, CardinalDirection::W, CardinalDirection::E], - vec![CardinalDirection::S, CardinalDirection::W, CardinalDirection::E, CardinalDirection::N], - vec![CardinalDirection::W, CardinalDirection::E, CardinalDirection::N, CardinalDirection::S], - vec![CardinalDirection::E, CardinalDirection::N, CardinalDirection::S, CardinalDirection::W], + vec![ + CardinalDirection::N, + CardinalDirection::S, + CardinalDirection::W, + CardinalDirection::E, + ], + vec![ + CardinalDirection::S, + CardinalDirection::W, + CardinalDirection::E, + CardinalDirection::N, + ], + vec![ + CardinalDirection::W, + CardinalDirection::E, + CardinalDirection::N, + CardinalDirection::S, + ], + vec![ + CardinalDirection::E, + CardinalDirection::N, + CardinalDirection::S, + CardinalDirection::W, + ], ]; for r in 0..rounds { @@ -61,8 +81,20 @@ pub fn run(input: &str, rounds: u32, round_count: bool) -> u32 { panic!("no result"); } - let (x_min, x_max) = elves.elves.iter().map(|e| e.x).minmax().into_option().unwrap(); - let (y_min, y_max) = elves.elves.iter().map(|e| e.y).minmax().into_option().unwrap(); + let (x_min, x_max) = elves + .elves + .iter() + .map(|e| e.x) + .minmax() + .into_option() + .unwrap(); + let (y_min, y_max) = elves + .elves + .iter() + .map(|e| e.y) + .minmax() + .into_option() + .unwrap(); let w = (x_max - x_min + 1) as u32; let h = (y_max - y_min + 1) as u32; @@ -105,10 +137,26 @@ impl ElfMap { fn is_free(&self, p: &Point, cd: CardinalDirection) -> bool { let adjacent = match cd { - CardinalDirection::N => vec![CompassDirection::N, CompassDirection::NE, CompassDirection::NW], - CardinalDirection::S => vec![CompassDirection::S, CompassDirection::SE, CompassDirection::SW], - CardinalDirection::W => vec![CompassDirection::W, CompassDirection::NW, CompassDirection::SW], - CardinalDirection::E => vec![CompassDirection::E, CompassDirection::NE, CompassDirection::SE], + CardinalDirection::N => vec![ + CompassDirection::N, + CompassDirection::NE, + CompassDirection::NW, + ], + CardinalDirection::S => vec![ + CompassDirection::S, + CompassDirection::SE, + CompassDirection::SW, + ], + CardinalDirection::W => vec![ + CompassDirection::W, + CompassDirection::NW, + CompassDirection::SW, + ], + CardinalDirection::E => vec![ + CompassDirection::E, + CompassDirection::NE, + CompassDirection::SE, + ], }; for d in adjacent { @@ -123,8 +171,20 @@ impl ElfMap { #[allow(dead_code)] fn dump(&self) { - let (x_min, x_max) = self.elves.iter().map(|e| e.x).minmax().into_option().unwrap(); - let (y_min, y_max) = self.elves.iter().map(|e| e.y).minmax().into_option().unwrap(); + let (x_min, x_max) = self + .elves + .iter() + .map(|e| e.x) + .minmax() + .into_option() + .unwrap(); + let (y_min, y_max) = self + .elves + .iter() + .map(|e| e.y) + .minmax() + .into_option() + .unwrap(); for y in y_min..=y_max { for x in x_min..=x_max { @@ -143,7 +203,9 @@ impl ElfMap { impl ElfMap { fn new() -> Self { - ElfMap { elves: HashSet::new() } + ElfMap { + elves: HashSet::new(), + } } fn parse(s: &str) -> Self { @@ -152,14 +214,15 @@ impl ElfMap { for (y, line) in s.lines().enumerate() { for (x, c) in line.chars().enumerate() { if c == '#' { - elves.insert(Point { x: x as i32, y: y as i32 }); + elves.insert(Point { + x: x as i32, + y: y as i32, + }); } } } - ElfMap { - elves - } + ElfMap { elves } } } diff --git a/src/bin/24.rs b/src/bin/24.rs index 3f4675c..7b457bf 100644 --- a/src/bin/24.rs +++ b/src/bin/24.rs @@ -1,6 +1,7 @@ -use std::iter::once; use aoc::point::CardinalDirection; -use aoc::shortest_path::{Graph, shortest_path_len}; +use aoc::shortest_path::{shortest_path_len, Graph}; +use hashbrown::HashMap; +use std::iter::once; pub fn part_one(input: &str) -> Option { Basin::parse(input, false).shortest_path() @@ -10,8 +11,8 @@ pub fn part_two(input: &str) -> Option { Basin::parse(input, true).shortest_path() } -type Minutes = u32; -type Coordinate = i16; +type Minutes = u16; +type Coordinate = i8; const DIRECTIONS: [(Coordinate, Coordinate); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)]; @@ -53,7 +54,10 @@ impl Blizzard { CardinalDirection::N => y = (y + h - mh) % h, }; - Point { x: x as Coordinate, y: y as Coordinate } + Point { + x: x as Coordinate, + y: y as Coordinate, + } } } @@ -63,7 +67,8 @@ struct Basin { end: Point, width: Coordinate, height: Coordinate, - blizzards: Vec, + blizzards_by_row: HashMap>, + blizzards_by_col: HashMap>, go_back_to_start: bool, } @@ -71,17 +76,32 @@ impl Basin { fn parse(s: &str, go_back_to_start: bool) -> Self { let lines: Vec<_> = s.lines().filter(|l| l.starts_with('#')).collect(); - let mut blizzards = Vec::::new(); + let mut blizzards_by_row = HashMap::>::new(); + let mut blizzards_by_col = HashMap::>::new(); for (y, l) in lines.iter().skip(1).take(lines.len() - 2).enumerate() { let line = &l.as_bytes()[1..l.len() - 1]; for (x, &c) in line.iter().enumerate() { if c != b'.' { - blizzards.push(Blizzard { - pos: Point { x: x as Coordinate, y: y as Coordinate }, + let blizzard = Blizzard { + pos: Point { + x: x as Coordinate, + y: y as Coordinate, + }, dir: CardinalDirection::for_code(c as char), - }); + }; + + match blizzard.dir { + CardinalDirection::N | CardinalDirection::S => blizzards_by_col + .entry(blizzard.pos.x) + .or_default() + .push(blizzard), + CardinalDirection::W | CardinalDirection::E => blizzards_by_row + .entry(blizzard.pos.y) + .or_default() + .push(blizzard), + } } } } @@ -90,10 +110,14 @@ impl Basin { let height = (lines.len() - 2) as Coordinate; Basin { start: Point { x: 0, y: -1 }, - end: Point { x: width - 1, y: height }, + end: Point { + x: width - 1, + y: height, + }, width, height, - blizzards, + blizzards_by_row, + blizzards_by_col, go_back_to_start, } } @@ -104,7 +128,23 @@ impl Basin { } else if p.x < 0 || p.y < 0 || p.x >= self.width || p.y >= self.height { false } else { - !self.blizzards.iter().any(|b| b.position(minutes, self.width, self.height) == p) + if let Some(blizzards) = self.blizzards_by_col.get(&p.x) { + if blizzards + .iter() + .any(|b| b.position(minutes, self.width, self.height) == p) + { + return false; + } + } + if let Some(blizzards) = self.blizzards_by_row.get(&p.y) { + if blizzards + .iter() + .any(|b| b.position(minutes, self.width, self.height) == p) + { + return false; + } + } + return true; } } @@ -114,7 +154,7 @@ impl Basin { minutes: 0, state: TripState::Initial, }; - shortest_path_len(self, start).map(|x|x.1) + shortest_path_len(self, start).map(|x| x.1) } } @@ -122,7 +162,8 @@ impl Graph for Basin { type Node = SearchState; fn is_solution(&self, node: &Self::Node) -> bool { - node.pos == self.end && (!self.go_back_to_start || node.state == TripState::VisitedStartAfterEnd) + node.pos == self.end + && (!self.go_back_to_start || node.state == TripState::VisitedStartAfterEnd) } fn collect_neighbors(&self, node: &Self::Node, neighbors: &mut Vec<(Self::Node, u32)>) { @@ -136,23 +177,35 @@ impl Graph for Basin { state: current_state, }; - let move_states = DIRECTIONS.iter() - .map(move |(dx, dy)| { - use TripState::*; - - let pos = current_pos.towards(*dx, *dy); - SearchState { - pos, - minutes: current_minutes + 1, - state: match current_state { - Initial => if pos == self.end { VisitedEnd } else { Initial } - VisitedEnd => if pos == self.start { VisitedStartAfterEnd } else { VisitedEnd } - VisitedStartAfterEnd => VisitedStartAfterEnd - }, - } - }); + let move_states = DIRECTIONS.iter().map(move |(dx, dy)| { + use TripState::*; + + let pos = current_pos.towards(*dx, *dy); + SearchState { + pos, + minutes: current_minutes + 1, + state: match current_state { + Initial => { + if pos == self.end { + VisitedEnd + } else { + Initial + } + } + VisitedEnd => { + if pos == self.start { + VisitedStartAfterEnd + } else { + VisitedEnd + } + } + VisitedStartAfterEnd => VisitedStartAfterEnd, + }, + } + }); - let cs = move_states.chain(once(wait_state)) + let cs = move_states + .chain(once(wait_state)) .filter(|s| self.is_empty(s.pos, s.minutes)) .map(|n| (n, 1)); @@ -187,7 +240,10 @@ mod tests { fn test_blizzard_position_right() { let w = 7; let h = 5; - let blizzard = Blizzard { pos: Point { x: 2, y: 4 }, dir: CardinalDirection::E }; + let blizzard = Blizzard { + pos: Point { x: 2, y: 4 }, + dir: CardinalDirection::E, + }; assert_eq!(Point { x: 2, y: 4 }, blizzard.position(0, w, h)); assert_eq!(Point { x: 3, y: 4 }, blizzard.position(1, w, h)); diff --git a/src/bin/25.rs b/src/bin/25.rs index 50619b3..03e2474 100644 --- a/src/bin/25.rs +++ b/src/bin/25.rs @@ -7,9 +7,11 @@ pub fn part_two(_input: &str) -> Option { } fn snafu_to_int(s: &str) -> i64 { - s.chars().rev().enumerate().map(|(i, c)| - snafu_char_to_digit(c) * 5_i64.pow(i as u32) - ).sum() + s.chars() + .rev() + .enumerate() + .map(|(i, c)| snafu_char_to_digit(c) * 5_i64.pow(i as u32)) + .sum() } fn int_to_snafu(mut num: i64) -> String { @@ -35,7 +37,13 @@ fn snafu_digit_to_char(c: i64) -> char { } fn snafu_char_to_digit(c: char) -> i64 { - SNAFU_DIGITS.iter().enumerate().find(|&(_, &val)| val == c).unwrap().0 as i64 - 2 + SNAFU_DIGITS + .iter() + .enumerate() + .find(|&(_, &val)| val == c) + .unwrap() + .0 as i64 + - 2 } fn main() { diff --git a/src/point.rs b/src/point.rs index 57d7b5a..a39da66 100644 --- a/src/point.rs +++ b/src/point.rs @@ -22,6 +22,12 @@ where } } +impl Point { + pub fn manhattan_distance(&self, p: &Self) -> u8 { + self.x.abs_diff(p.x) + self.y.abs_diff(p.y) + } +} + impl Point { pub fn manhattan_distance(&self, p: &Self) -> u16 { self.x.abs_diff(p.x) + self.y.abs_diff(p.y) @@ -109,7 +115,6 @@ pub enum CardinalDirection { } impl CardinalDirection { - pub fn for_code(c: char) -> Self { match c { '^' => Self::N, diff --git a/src/shortest_path.rs b/src/shortest_path.rs index f2d4c65..4d3c872 100644 --- a/src/shortest_path.rs +++ b/src/shortest_path.rs @@ -13,8 +13,8 @@ pub trait Graph { } pub fn shortest_path_len(g: &G, start: G::Node) -> Option<(G::Node, u32)> - where - G: Graph, +where + G: Graph, { let mut g_score = HashMap::::new(); let mut open_set = PriorityQueue::>::new();