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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! # Grove Positioning System
//!
//! We store the numbers in a triple nested `vec` of `vec` of `vec`. The initial size of each
//! vector is ∛5000 ~= 17, so that numbers are spread as evenly as possible.
//!
//! Numbes are stored in the leaf `vec`s. This greatly reduces the time to insert, remove and find
//! numbers, compared to storing all numbers in a single flat `vec`. Some further optimizations:
//! * The first and second level indices of a number change only when it moves, so these can be
//!   stored in a lookup array for fast access.
//! * The size of each first level `vec` is the the sum of the second level `vec`s contained
//!   inside. This is stored in the `skip` array to prevent recomputing on each move.
//!
//! This implementation is both faster and simpler than the previous version (preserved in the
//! commit history) that used an [order statistic tree](https://en.wikipedia.org/wiki/Order_statistic_tree),
//! although perhaps adding [balancing rotations](https://en.wikipedia.org/wiki/Tree_rotation)
//! to the tree would make it faster.
use crate::util::parse::*;

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

pub fn part1(input: &[i64]) -> i64 {
    decrypt(input, 1, 1)
}

pub fn part2(input: &[i64]) -> i64 {
    decrypt(input, 811589153, 10)
}

fn decrypt(input: &[i64], key: i64, rounds: usize) -> i64 {
    // Important nuance, size is one less because we don't consider the moving number.
    let size = input.len() - 1;
    // Another nuance, input contain duplicate numbers, so use index to refer to each number uniquely.
    let indices: Vec<_> = (0..input.len()).collect();
    // Pre-process the numbers, coverting any negative indices to positive indices that will wrap.
    // For example, -1 becomes 4998.
    let numbers: Vec<_> =
        input.iter().map(|n| (n * key).rem_euclid(size as i64) as usize).collect();

    // Store first and second level indices.
    let mut lookup = Vec::new();
    // Triple nested vec of numbers.
    let mut mixed = Vec::new();
    // Size of each first level element for convenience.
    let mut skip = Vec::new();

    // Break 5000 numbers into roughly equals chunks at each level. 289 = 17 * 17.
    for first in indices.chunks(289) {
        let mut outer = Vec::new();

        for second in first.chunks(17) {
            // Initial first and second level indices.
            (0..second.len()).for_each(|_| lookup.push((mixed.len(), outer.len())));

            // Leave some extra room, as mixing won't balance evenly.
            let mut inner = Vec::with_capacity(100);
            inner.extend_from_slice(second);

            outer.push(inner);
        }

        mixed.push(outer);
        skip.push(first.len());
    }

    for _ in 0..rounds {
        'mix: for index in 0..input.len() {
            // Quickly find the leaf vector storing the number.
            let number = numbers[index];
            let (first, second) = lookup[index];
            // Third level changes as other numbers are added and removed,
            // so needs to be checked each time.
            let third = mixed[first][second].iter().position(|&i| i == index).unwrap();

            // Find the offset of the number by adding the size of all previous `vec`s.
            let position = third
                + skip[0..first].iter().sum::<usize>()
                + mixed[first][0..second].iter().map(Vec::len).sum::<usize>();
            // Update our position, wrapping around if necessary.
            let mut next = (position + number) % size;

            // Remove number from current leaf vector, also updating the first level size.
            mixed[first][second].remove(third);
            skip[first] -= 1;

            // Find our new destination, by checking `vec`s in order until the total elements
            // are greater than our new index.
            for (first, outer) in mixed.iter_mut().enumerate() {
                if next > skip[first] {
                    next -= skip[first];
                } else {
                    for (second, inner) in outer.iter_mut().enumerate() {
                        if next > inner.len() {
                            next -= inner.len();
                        } else {
                            // Insert number into its new home.
                            inner.insert(next, index);
                            skip[first] += 1;
                            lookup[index] = (first, second);
                            continue 'mix;
                        }
                    }
                }
            }
        }
    }

    let indices: Vec<_> = mixed.into_iter().flatten().flatten().collect();
    let zeroth = indices.iter().position(|&i| numbers[i] == 0).unwrap();

    [1000, 2000, 3000]
        .iter()
        .map(|offset| (zeroth + offset) % indices.len())
        .map(|index| input[indices[index]] * key)
        .sum()
}