Day 11
This commit is contained in:
parent
638522da69
commit
e291f48c84
4 changed files with 172 additions and 0 deletions
61
2024/day11/Cargo.lock
generated
Normal file
61
2024/day11/Cargo.lock
generated
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# 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 = "day11"
|
||||||
|
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",
|
||||||
|
]
|
7
2024/day11/Cargo.toml
Normal file
7
2024/day11/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "day11"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rayon = "1.10.0"
|
26
2024/day11/README.md
Normal file
26
2024/day11/README.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Solution Day 11
|
||||||
|
|
||||||
|
This one was a tricky one. I actually needed a bit of inspiration. The initial idea was rather straightforward: implement the simulation rules as a single step, simulate
|
||||||
|
them x times for all stones in the input and count the number of stones in the end. But...
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
|
||||||
|
This one is easy. Implement the simple approach outlined above, get the result. Nothing special, because it's "just" 25 rounds, so we're good. My original implementation
|
||||||
|
was actually done based on the string values, because two of the rules are easy enough this was: comparing with `"0"` is just as easy as using a numerical value,
|
||||||
|
splitting the number of even length is even easier. The only issue is correctly handling the leading `0`, so in the end I did just do everything with `u64`, only
|
||||||
|
converting to string in order to split (which could technically also have been done with clever use of `log10()`, `/` and `%`, but I'm lazy).
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
This is where it all came crashing down. I do have a _very_ beefy laptop with 32 GB of RAM and still my naive approach was killed by the OOM killer after significant
|
||||||
|
amounts of runtime. Since the stones can be simulated in isolation, we don't actually have to store all of the results, but rather can just count them, so I rewrote
|
||||||
|
the algorithm to work recursivly, calculating the count of a stone depth first. This means, that we will never have to store the actual stones (we'll just hand them
|
||||||
|
into the recursive call), which keeps the memory under control. It doesn't solve the runtime issue, though. In the iterative program I had printed the round index
|
||||||
|
at some point and that made it abundantly clear, that this program would probably run the whole night.
|
||||||
|
|
||||||
|
Here is where I needed a bit of inspiration. I read through a few of the posts under the hashtag #adventofcode on the Fediverse and someone mentioned memoisation issues
|
||||||
|
they were having. So, a cache it is then? I implemented a simple in-memory cache, that maps `(stone, run number)` => `count in the end`. In order to keep the cache
|
||||||
|
size under control, I decided to go with `stone`s < 1000 for a start (that would at most give us ~75,000 entries in the cache, which should be OK). Well, turns out that
|
||||||
|
this is plenty. The program finished so quickly, that I initially thought I had made a mistake with the cache and didn't actually want to try the result. Much to my
|
||||||
|
surprise (I honestly let out a little scream of surprise, much to my wife's surprise in turn), the result was correct. The small cache is so efficient, that the
|
||||||
|
calculation runs way, way below a second on my laptop. Someone smarter than me can probably calculate the cache hit rate and why this thing is so efficient.
|
78
2024/day11/src/main.rs
Normal file
78
2024/day11/src/main.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn load_input() -> Vec<u64> {
|
||||||
|
std::fs::read_to_string("./input.txt")
|
||||||
|
.expect("Read file failed")
|
||||||
|
.trim()
|
||||||
|
.split(" ")
|
||||||
|
.filter(|s| s.len() > 0)
|
||||||
|
.map(|s| s.parse().unwrap())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_value(cache: &mut HashMap<(u64, u32), u64>, stone: u64, run: u32, count: u64) {
|
||||||
|
if stone < 1000 {
|
||||||
|
cache.insert((stone, run), count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(stone: u64, run: u32, cache: &mut HashMap<(u64, u32), u64>) -> u64 {
|
||||||
|
if cache.contains_key(&(stone, run)) {
|
||||||
|
return cache.get(&(stone, run)).unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if run == 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if stone == 0 {
|
||||||
|
let c = count(1, run - 1, cache);
|
||||||
|
cache_value(cache, stone, run, c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
let val_str = stone.to_string();
|
||||||
|
if val_str.len() % 2 == 0 {
|
||||||
|
let c = count(
|
||||||
|
val_str[0..val_str.len() / 2].parse::<u64>().unwrap(),
|
||||||
|
run - 1,
|
||||||
|
cache,
|
||||||
|
) + count(
|
||||||
|
val_str[val_str.len() / 2..].parse::<u64>().unwrap(),
|
||||||
|
run - 1,
|
||||||
|
cache,
|
||||||
|
);
|
||||||
|
cache_value(cache, stone, run, c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
let c = count(stone * 2024, run - 1, cache);
|
||||||
|
cache_value(cache, stone, run, c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task1() {
|
||||||
|
let stones = load_input();
|
||||||
|
|
||||||
|
let mut cache: HashMap<(u64, u32), u64> = HashMap::new();
|
||||||
|
let count = stones
|
||||||
|
.iter()
|
||||||
|
.fold(0, |sum, &stone| sum + count(stone, 25, &mut cache));
|
||||||
|
|
||||||
|
println!("Task 1: # stones: {}", count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task2() {
|
||||||
|
let stones = load_input();
|
||||||
|
|
||||||
|
let mut cache: HashMap<(u64, u32), u64> = HashMap::new();
|
||||||
|
let count = stones
|
||||||
|
.iter()
|
||||||
|
.fold(0, |sum, &stone| sum + count(stone, 75, &mut cache));
|
||||||
|
|
||||||
|
println!("Task 2: # stones: {}", count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
task1();
|
||||||
|
task2();
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue