Skip to content

Commit 28b35cb

Browse files
committed
Faster approach caching precomputed blocks of jump offsets
1 parent 4598de9 commit 28b35cb

File tree

4 files changed

+106
-40
lines changed

4 files changed

+106
-40
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Place input files in `input/yearYYYY/dayDD.txt` including leading zeroes. For ex
5353
## Performance
5454

5555
Benchmarks are measured using the built-in `cargo bench` tool run on an [Apple M2 Max][apple-link].
56-
All 225 solutions from 2023 to 2015 complete sequentially in **594 milliseconds**.
56+
All 225 solutions from 2023 to 2015 complete sequentially in **581 milliseconds**.
5757
Interestingly 84% of the total time is spent on just 9 solutions.
5858
Performance is reasonable even on older hardware, for example a 2011 MacBook Pro with an
5959
[Intel i7-2720QM][intel-link] processor takes 3.5 seconds to run the same 225 solutions.
@@ -68,7 +68,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
6868
| [2020](#2020) | 272 |
6969
| [2019](#2019) | 16 |
7070
| [2018](#2018) | 35 |
71-
| [2017](#2017) | 102 |
71+
| [2017](#2017) | 89 |
7272
| [2016](#2016) | 120 |
7373
| [2015](#2015) | 24 |
7474

@@ -274,7 +274,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
274274
| 2 | [Corruption Checksum](https://adventofcode.com/2017/day/2) | [Source](src/year2017/day02.rs) | 2 |
275275
| 3 | [Spiral Memory](https://adventofcode.com/2017/day/3) | [Source](src/year2017/day03.rs) | 2 |
276276
| 4 | [High-Entropy Passphrases](https://adventofcode.com/2017/day/4) | [Source](src/year2017/day04.rs) | 98 |
277-
| 5 | [A Maze of Twisty Trampolines, All Alike](https://adventofcode.com/2017/day/5) | [Source](src/year2017/day05.rs) | 36000 |
277+
| 5 | [A Maze of Twisty Trampolines, All Alike](https://adventofcode.com/2017/day/5) | [Source](src/year2017/day05.rs) | 22000 |
278278
| 6 | [Memory Reallocation](https://adventofcode.com/2017/day/6) | [Source](src/year2017/day06.rs) | 81 |
279279
| 7 | [Recursive Circus](https://adventofcode.com/2017/day/7) | [Source](src/year2017/day07.rs) | 93 |
280280
| 8 | [I Heard You Like Registers](https://adventofcode.com/2017/day/8) | [Source](src/year2017/day08.rs) | 47 |

docs/pie-2017.svg

+13-12
Loading

docs/pie-all.svg

+12-12
Loading

src/year2017/day05.rs

+78-13
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,105 @@
11
//! # A Maze of Twisty Trampolines, All Alike
2+
//!
3+
//! Part one brute forces the jumps. For part two we can make an observation that the jumps offsets
4+
//! will eventually flip flop between 2 or 3 starting from the beginning, for example:
5+
//!
6+
//! ```none
7+
//! 2 3 2 3 -1
8+
//! ```
9+
//!
10+
//! The twos and threes can be represented in binary compact form, using 0 for 2 and 1 for 3:
11+
//!
12+
//! ```none
13+
//! 0101
14+
//! ```
15+
//!
16+
//! We then precompute all possible combination for blocks of size 16, using this to accelerate
17+
//! part two.
218
use crate::util::parse::*;
319

20+
const WIDTH: usize = 16;
21+
const LENGTH: usize = 1 << WIDTH;
22+
423
pub fn parse(input: &str) -> Vec<i32> {
524
input.iter_signed().collect()
625
}
726

8-
pub fn part1(input: &[i32]) -> u32 {
27+
/// Brute force implementation.
28+
pub fn part1(input: &[i32]) -> usize {
929
let mut jump = input.to_vec();
30+
let mut total = 0;
1031
let mut index = 0;
11-
let mut result = 0;
1232

1333
while index < jump.len() {
1434
let next = index.wrapping_add(jump[index] as usize);
1535
jump[index] += 1;
36+
total += 1;
1637
index = next;
17-
result += 1;
1838
}
1939

20-
result
40+
total
2141
}
2242

23-
pub fn part2(input: &[i32]) -> u32 {
43+
#[expect(clippy::needless_range_loop)]
44+
pub fn part2(input: &[i32]) -> usize {
2445
let mut jump = input.to_vec();
46+
let mut total = 0;
2547
let mut index = 0;
26-
let mut result = 0;
48+
49+
let mut fine = 0;
50+
let mut coarse = 0;
51+
let mut compact = Vec::new();
52+
let mut cache = vec![[(0_u16, 0_u8, 0_u8); LENGTH]; WIDTH];
53+
54+
// Precompute all possible combinations. For each binary starting number we can start at any
55+
// offset from 0..16.
56+
for i in 0..WIDTH {
57+
for j in 0..LENGTH {
58+
let mut offset = i as u16;
59+
let mut value = j as u16;
60+
let mut steps = 0;
61+
62+
while offset < 16 {
63+
value ^= 1 << offset;
64+
steps += 1;
65+
offset += 3 - ((value >> offset) & 1);
66+
}
67+
68+
cache[i][j] = (value, steps, offset as u8 - i as u8);
69+
}
70+
}
2771

2872
while index < jump.len() {
29-
let next = index.wrapping_add(jump[index] as usize);
30-
if jump[index] < 3 {
31-
jump[index] += 1;
73+
if index < coarse {
74+
// Index lies withing precomputed blocks.
75+
let base = index / 16;
76+
let offset = index % 16;
77+
let value = compact[base] as usize;
78+
let (next, steps, delta) = cache[offset][value];
79+
80+
compact[base] = next;
81+
total += steps as usize;
82+
index += delta as usize;
3283
} else {
33-
jump[index] -= 1;
84+
// Fall back to part one approach.
85+
let next = index.wrapping_add(jump[index] as usize);
86+
jump[index] += if jump[index] == 3 { -1 } else { 1 };
87+
total += 1;
88+
89+
// The frontier of twos and threes advances through the jump offsets.
90+
// Each time it crosses a block of 16 add to the compact binary representation.
91+
if jump[index] == 2 && index == fine {
92+
fine += 1;
93+
if fine % 16 == 0 {
94+
let value = (coarse..fine).rev().fold(0, |acc, i| (acc << 1) | (jump[i] & 1));
95+
coarse = fine;
96+
compact.push(value as u16);
97+
}
98+
}
99+
100+
index = next;
34101
}
35-
index = next;
36-
result += 1;
37102
}
38103

39-
result
104+
total
40105
}

0 commit comments

Comments
 (0)