Skip to main content

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 std::mem::replace;
13
14use crate::util::parse::*;
15
16const THRESHOLD: usize = 0x10000;
17
18pub fn parse(input: &str) -> Vec<usize> {
19    input.iter_unsigned().collect()
20}
21
22pub fn part1(input: &[usize]) -> usize {
23    play(input, 2020)
24}
25
26pub fn part2(input: &[usize]) -> usize {
27    play(input, 30_000_000)
28}
29
30fn play(input: &[usize], rounds: usize) -> usize {
31    let (last, starting) = input.split_last().unwrap();
32
33    let mut last = *last;
34    let mut zeroth = 0;
35    let mut spoken = vec![0; rounds];
36    let mut seen = vec![0_u64; rounds / 64];
37
38    for (i, &n) in starting.iter().enumerate() {
39        if n == 0 {
40            zeroth = i + 1;
41        } else {
42            spoken[n] = (i + 1) as u32;
43        }
44    }
45
46    for i in input.len()..rounds {
47        if last == 0 {
48            // Handle zero specially as it occurs the most.
49            let previous = replace(&mut zeroth, i);
50            last = if previous == 0 { 0 } else { i - previous };
51        } else if last < THRESHOLD {
52            // Smaller numbers occur frequently so skip previously seen bitset check.
53            let previous = replace(&mut spoken[last], i as u32) as usize;
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 = replace(&mut spoken[last], i as u32) as usize;
69                last = i - previous;
70            }
71        }
72    }
73
74    last
75}