diff --git a/2024/day6/Cargo.lock b/2024/day6/Cargo.lock index a16c0bf..0e6c06a 100644 --- a/2024/day6/Cargo.lock +++ b/2024/day6/Cargo.lock @@ -2,6 +2,60 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "day6" version = "0.1.0" +dependencies = [ + "rayon", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] diff --git a/2024/day6/Cargo.toml b/2024/day6/Cargo.toml index 46c0229..7ef1ea8 100644 --- a/2024/day6/Cargo.toml +++ b/2024/day6/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] +rayon = "1.10.0" diff --git a/2024/day6/README.md b/2024/day6/README.md index a7864b1..58843b0 100644 --- a/2024/day6/README.md +++ b/2024/day6/README.md @@ -46,5 +46,7 @@ record all turns made somewhere, I just copy and save every turn step (position this step is already in the list of previous turns, I've actually entered a loop and can stop. This then leads to the correct number of possible obstacle postions. -One possible optimization would be to use the result map from task 1 to only check the positions, where the guard actually reaches, -but the runtime of the algorithms is still in the low seconds on my laptop, so, again, not worth the effort... \ No newline at end of file +After my son challenged me to optimize the thing a bit, the program got it's pre-check to only ever simulate the guard walk for positions +that are actually reachable on the original map and I took the chance to play with [rayon](https://github.com/rayon-rs/rayon). Initially +the algorithm placing the potential obstacles was just two nested loops. Replacing the inner loop with a rayon parallel iterator and +thereby distributing it to multiple CPU cores \ No newline at end of file diff --git a/2024/day6/src/main.rs b/2024/day6/src/main.rs index 0c4037a..4cf4490 100644 --- a/2024/day6/src/main.rs +++ b/2024/day6/src/main.rs @@ -1,3 +1,5 @@ +use rayon::prelude::*; + type Map = Vec>; fn load_map() -> Map { @@ -142,42 +144,59 @@ fn task1() { } fn task2() { - let map_orig = load_map(); + let mut map_orig = load_map(); let mut possible_positions = 0; + // mark all fields touched by the guard + let mut guard_opt = Some(get_guard_position(&map_orig)); + loop { + if let Some(guard) = guard_opt { + if map_orig[guard.y][guard.x] != '^' { + map_orig[guard.y][guard.x] = 'X'; + } + guard_opt = step_guard(&map_orig, guard); + } else { + break; + } + } + // try out all possible positions and add some obstacle there for y in 0..map_orig.len() { - for x in 0..map_orig[y].len() { - let mut map = map_orig.clone(); - let mut guard_opt = Some(get_guard_position(&map)); + possible_positions += (0..map_orig[y].len()) + .into_par_iter() + .fold( + || 0, + |sum, x| { + let mut map = map_orig.clone(); + let mut guard_opt = Some(get_guard_position(&map)); - if map[x][y] == '#' || map[x][y] == '^' { - continue; // no need to check here, there's already an obstacle or the guard - } - let current_element = map[x][y]; - map[x][y] = '#'; - let mut existing_turns = Vec::::new(); - loop { - if let Some(guard) = guard_opt.as_ref() { - let new_guard_pos = step_guard(&map, guard.clone()); - if let Some(np) = new_guard_pos.as_ref() { - if np.direction != guard.direction { - // we changed direction -> check whether we've changed the direction in the exact same way here before, which would mean we're in a loop - if existing_turns.contains(np) { - possible_positions += 1; - break; - } else { - existing_turns.push(np.clone()); // remember the turn + if map[x][y] != 'X' { + return sum; // no need to check here, there's already an obstacle or the guard + } + map[x][y] = '#'; + let mut existing_turns = Vec::::new(); + loop { + if let Some(guard) = guard_opt.as_ref() { + let new_guard_pos = step_guard(&map, guard.clone()); + if let Some(np) = new_guard_pos.as_ref() { + if np.direction != guard.direction { + // we changed direction -> check whether we've changed the direction in the exact same way here before, which would mean we're in a loop + if existing_turns.contains(np) { + return sum + 1; + } else { + existing_turns.push(np.clone()); // remember the turn + } + } } + guard_opt = new_guard_pos; + } else { + break; } } - guard_opt = new_guard_pos; - } else { - break; - } - } - map[x][y] = current_element; - } + sum + }, + ) + .sum::(); } println!("Task 2: Possible positions: {possible_positions}");