Skip to content

Commit cc929bc

Browse files
committed
faster Day 11
1 parent 9d465d8 commit cc929bc

File tree

4 files changed

+148
-40
lines changed

4 files changed

+148
-40
lines changed

Cargo.lock

Lines changed: 65 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mygrid = { version = "0.0.1", path = "mygrid" }
3333
nalgebra = "0.33.2"
3434
num = "0.4.3"
3535
object-pool = "0.6.0"
36+
phf = { version = "0.11.2", features = ["macros"] }
3637
pico-args = "0.5.0"
3738
rayon = "1.10.0"
3839
regex = "1.11.1"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
2121
| [Day 8](./src/bin/08.rs) | `7.4µs` | `15.5µs` |
2222
| [Day 9](./src/bin/09.rs) | `149.0µs` | `6.5ms` |
2323
| [Day 10](./src/bin/10.rs) | `31.1µs` | `28.6µs` |
24-
| [Day 11](./src/bin/11.rs) | `284.6µs` | `12.8ms` |
24+
| [Day 11](./src/bin/11.rs) | `71.0µs` | `766.0µs` |
2525
| [Day 12](./src/bin/12.rs) | `434.7µs` | `674.6µs` |
2626
| [Day 13](./src/bin/13.rs) | `90.9µs` | `91.8µs` |
2727
| [Day 14](./src/bin/14.rs) | `30.8µs` | `3.5ms` |
2828
| [Day 15](./src/bin/15.rs) | `119.7µs` | `591.0µs` |
2929
| [Day 16](./src/bin/16.rs) | `193.4µs` | `22.6ms` |
3030

31-
**Total: 54.12ms**
31+
**Total: 41.87ms**
3232
<!--- benchmarking table --->
3333

3434
---

src/bin/11.rs

Lines changed: 80 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,104 @@
1-
use memoize::memoize;
2-
use num::traits::Euclid;
1+
use rustc_hash::FxHashMap;
32

43
advent_of_code::solution!(11);
54

65
type Stone = u64;
76
type Step = u8;
87

9-
#[memoize]
10-
// have a memoize cache buster to fix timing multiple runs
11-
fn blink_rec(_cache_buster: u64, stone: Stone, times: Step) -> u64 {
12-
if times == 0 {
13-
return 1;
14-
}
8+
const MAX_STONE_COUNT: usize = 5000;
9+
const MAX_DIGIT_COUNT: usize = 40;
1510

16-
if stone == 0 {
17-
return blink_rec(_cache_buster, 1, times - 1);
11+
// take advantage of the fact that there is a limited number of stone numbers
12+
// found by @maneatingape: https://github.com/maneatingape/advent-of-code-rust/blob/main/src/year2024/day11.rs#L3
13+
struct StoneIdxPool {
14+
count: usize,
15+
stone_num_to_stone_idx: FxHashMap<Stone, usize>,
16+
stone_idx_to_stone_num: [Stone; MAX_STONE_COUNT],
17+
}
18+
impl StoneIdxPool {
19+
#[inline]
20+
fn new() -> Self {
21+
Self {
22+
count: 0,
23+
stone_num_to_stone_idx: FxHashMap::default(),
24+
stone_idx_to_stone_num: [0; MAX_STONE_COUNT],
25+
}
1826
}
1927

20-
let mut digit_count = 0;
21-
let mut temp = stone;
22-
while temp > 0 {
23-
digit_count += 1;
24-
temp /= 10;
25-
}
26-
// makes p1 2x slower but p2 0.2x faster
27-
// let digit_count = stone.checked_ilog10().unwrap_or(0);
28-
if digit_count % 2 == 0 {
29-
let divisor: u64 = 10_u64.pow(digit_count / 2);
30-
let (upper, lower) = stone.div_rem_euclid(&divisor);
31-
return blink_rec(_cache_buster, lower, times - 1)
32-
+ blink_rec(_cache_buster, upper, times - 1);
28+
#[inline]
29+
fn get_idx(&mut self, stone_num: Stone) -> usize {
30+
if let Some(idx) = self.stone_num_to_stone_idx.get(&stone_num) {
31+
return *idx;
32+
}
33+
34+
let idx = self.count;
35+
self.stone_idx_to_stone_num[idx] = stone_num;
36+
self.stone_num_to_stone_idx.insert(stone_num, idx);
37+
self.count += 1;
38+
idx
3339
}
3440

35-
return blink_rec(_cache_buster, stone * 2024, times - 1);
41+
#[inline]
42+
fn get_stone_num(&self, idx: usize) -> Stone {
43+
self.stone_idx_to_stone_num[idx]
44+
}
3645
}
3746

38-
static mut GLOBAL_SEQ: u64 = 0;
39-
4047
fn solve(input: &str, times: Step) -> u64 {
41-
let cache_buster = unsafe { GLOBAL_SEQ };
42-
unsafe {
43-
GLOBAL_SEQ += 1;
48+
let mut idx_pool = StoneIdxPool::new();
49+
50+
let mut divisors = [0; MAX_DIGIT_COUNT];
51+
for i in 1..MAX_DIGIT_COUNT {
52+
divisors[i] = 10_u64.pow(i as u32 / 2);
4453
}
45-
let stones = input
54+
55+
let mut stone_count: [Stone; MAX_STONE_COUNT] = [0; MAX_STONE_COUNT];
56+
input
4657
.split_whitespace()
4758
.filter(|p| !p.is_empty())
48-
.map(|s| s.parse::<u64>())
59+
.map(|s| s.parse::<Stone>())
4960
.filter(|r| r.is_ok())
5061
.map(|r| r.unwrap())
51-
.collect::<Vec<_>>();
62+
.for_each(|stone_num| {
63+
stone_count[idx_pool.get_idx(stone_num)] += 1;
64+
});
65+
66+
for _ in 0..times {
67+
let mut new_stone_count: [Stone; MAX_STONE_COUNT] = [0; MAX_STONE_COUNT];
68+
stone_count
69+
.iter()
70+
.enumerate()
71+
.filter(|(_, &count)| count > 0)
72+
.for_each(|(stone_idx, &count)| {
73+
let stone_num = idx_pool.get_stone_num(stone_idx);
74+
if stone_num == 0 {
75+
new_stone_count[idx_pool.get_idx(1)] += count;
76+
return;
77+
}
78+
79+
// this is a bit faster than ilog10
80+
let mut digit_count = 0;
81+
let mut temp = stone_num;
82+
while temp > 0 {
83+
digit_count += 1;
84+
temp /= 10;
85+
}
86+
87+
if digit_count % 2 == 0 {
88+
let divisor = divisors[digit_count];
89+
new_stone_count[idx_pool.get_idx(stone_num / divisor)] += count;
90+
new_stone_count[idx_pool.get_idx(stone_num % divisor)] += count;
5291

53-
let count = stones
54-
.iter() // so fast that parallel is slower
55-
.map(|start_stone| blink_rec(cache_buster, *start_stone, times))
56-
.sum::<u64>();
92+
return;
93+
}
94+
95+
new_stone_count[idx_pool.get_idx(stone_num * 2024)] += count;
96+
});
97+
98+
stone_count = new_stone_count;
99+
}
57100

58-
return count;
101+
stone_count.iter().sum()
59102
}
60103

61104
pub fn part_one(input: &str) -> Option<u64> {

0 commit comments

Comments
 (0)