aoc/year2020/day15.rs
1//! # Rambunctious Recitation
2//!
3//! For efficiency the `vec` storing the last previously spoken turn of numbers is `u32`.
4//! Each difference is at least one so zero is used as a special value to indicate numbers not
5//! seen before.
6//!
7//! To speed things up even more, we notice that most large numbers over a certain threshold are
8//! spoken only once. Storing if numbers have been seen before in a compact bitset prevents
9//! expensive reads to main memory and halves the time needed for the solution.
10//!
11//! Zero occurs the most so storing it as a dedicated variable saves another 2% of execution time.
12use crate::util::parse::*;
13
14const THRESHOLD: usize = 0x10000;
15
16pub fn parse(input: &str) -> Vec<usize> {
17 input.iter_unsigned().collect()
18}
19
20pub fn part1(input: &[usize]) -> usize {
21 play(input, 2020)
22}
23
24pub fn part2(input: &[usize]) -> usize {
25 play(input, 30_000_000)
26}
27
28fn play(input: &[usize], rounds: usize) -> usize {
29 let size = input.len() - 1;
30
31 let mut last = input[size];
32 let mut zeroth = 0;
33 let mut spoken = vec![0; rounds];
34 let mut seen = vec![0_u64; rounds / 64];
35
36 for i in 0..size {
37 if input[i] == 0 {
38 zeroth = i + 1;
39 } else {
40 spoken[input[i]] = (i + 1) as u32;
41 }
42 }
43
44 for i in input.len()..rounds {
45 if last == 0 {
46 // Handle zero specially as it occurs the most.
47 let previous = zeroth;
48 zeroth = i;
49 last = if previous == 0 { 0 } else { i - previous };
50 } else if last < THRESHOLD {
51 // Smaller numbers occur frequently so skip previously seen bitset check.
52 let previous = spoken[last] as usize;
53 spoken[last] = i as u32;
54 last = if previous == 0 { 0 } else { i - previous };
55 } else {
56 // An array of 30 million `u32`s needs 120 MB of memory which exceeds most caches.
57 // Writing and reading to random locations in this large array goes to main memory
58 // which is slow. Store if a number has been seen before in a compact bitset,
59 // needing only a more cache friendly 4 MB.
60 let base = last / 64;
61 let mask = 1 << (last % 64);
62
63 if seen[base] & mask == 0 {
64 seen[base] |= mask;
65 spoken[last] = i as u32;
66 last = 0;
67 } else {
68 let previous = spoken[last] as usize;
69 spoken[last] = i as u32;
70 last = i - previous;
71 }
72 }
73 }
74
75 last
76}