parallelize day 6 solution

This commit is contained in:
Markus Brueckner 2024-12-06 23:03:08 +01:00
parent 51dc523cf6
commit c44da97678
4 changed files with 106 additions and 30 deletions

54
2024/day6/Cargo.lock generated
View file

@ -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",
]

View file

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
rayon = "1.10.0"

View file

@ -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...
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

View file

@ -1,3 +1,5 @@
use rayon::prelude::*;
type Map = Vec<Vec<char>>;
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::<GuardPosition>::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::<GuardPosition>::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::<u32>();
}
println!("Task 2: Possible positions: {possible_positions}");