1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//! # Rambunctious Recitation
//!
//! Hybrid solution that uses both a `vec` and [`FastMap`] to store previously seen values.
//! This approach is faster than using either data structure alone. The threshold is chosen so that
//! about 85% of values are stored in the `vec`.
//!
//! To save space the `vec` is `u32` instead of `usize`. Each difference is at least one so we can
//! use zero as a special value to indicate numbers not seen before.
//!
//! Accessing the map uses the [`Entry`] method as this reduces two key lookups to one.
//!
//! [`FastMap`]: crate::util::hash
//! [`Entry`]: std::collections::hash_map::Entry
use crate::util::hash::*;
use crate::util::parse::*;

const THRESHOLD: usize = 1_000_000;

pub fn parse(input: &str) -> Vec<usize> {
    input.iter_unsigned().collect()
}

pub fn part1(input: &[usize]) -> usize {
    play(input, 2020)
}

pub fn part2(input: &[usize]) -> usize {
    play(input, 30_000_000)
}

fn play(input: &[usize], rounds: usize) -> usize {
    let size = input.len() - 1;
    let mut last = input[size];

    let mut spoken_low = vec![0; rounds.min(THRESHOLD)];
    let mut spoken_high = FastMap::with_capacity(rounds / 5);

    for i in 0..size {
        spoken_low[input[i]] = (i + 1) as u32;
    }

    for i in input.len()..rounds {
        if last < THRESHOLD {
            let previous = spoken_low[last] as usize;
            spoken_low[last] = i as u32;
            last = if previous == 0 { 0 } else { i - previous };
        } else {
            spoken_high
                .entry(last as u32)
                .and_modify(|previous| {
                    last = i - *previous as usize;
                    *previous = i as u32;
                })
                .or_insert_with(|| {
                    last = 0;
                    i as u32
                });
        }
    }

    last
}