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
//! # Universal Orbit Map
//!
//! Each object name is 3 characters long, using the characters `A` to `Z` and `0` to `9`.
//! This is only 36 ^ 3 = 46656 possibilities, so we can use
//! [perfect hashing](https://en.wikipedia.org/wiki/Perfect_hash_function) to store contiguous
//! indices for each object, allowing us to lookup a perfect *minimal* hash for each object.
//!
//! This is twice as fast as using a [`FastMap`] to lookup the indices.
//!
//! [`FastMap`]: crate::util::hash

/// Convert 3 character object names to contiguous indices for faster lookup.
pub fn parse(input: &str) -> Vec<usize> {
    // Convert 'A'.."Z" and '0'..'9' to a number between 0 and 36.
    let digit = |b: u8| {
        if b.is_ascii_digit() {
            (b - b'0') as usize
        } else {
            (10 + b - b'A') as usize
        }
    };

    // Hash each 3 character object name.
    let perfect_hash = |object: &str| -> usize {
        let bytes = object.as_bytes();
        digit(bytes[0]) + 36 * digit(bytes[1]) + 1296 * digit(bytes[2])
    };

    // Pre-seed known indices for objects that we need to specifically lookup later.
    let mut indices = [0_u16; 36 * 36 * 36];
    indices[perfect_hash("COM")] = 1;
    indices[perfect_hash("SAN")] = 2;
    indices[perfect_hash("YOU")] = 3;
    let mut current = 4;

    // Assign sequential indices to each object the first time that we encounter it.
    // 0 is used as a special "empty" value.
    let mut lookup = |s: &str| {
        let hash = perfect_hash(s);
        if indices[hash] == 0 {
            let previous = current;
            indices[hash] = current;
            current += 1;
            previous as usize
        } else {
            indices[hash] as usize
        }
    };

    // Build parent-child relationships for each object. Add one extra for the unused 0 special
    // value and another as there is always one more object than input lines.
    let lines: Vec<_> = input.lines().collect();
    let mut parent = vec![0; lines.len() + 2];

    for line in lines {
        let left = lookup(&line[..3]);
        let right = lookup(&line[4..]);
        parent[right] = left;
    }

    parent
}

/// Recusively follow parent relationships all the way to the root COM object. Cache each object's
/// depth in order to avoid unecessary work.
pub fn part1(input: &[usize]) -> usize {
    fn orbits(parent: &[usize], cache: &mut [Option<usize>], index: usize) -> usize {
        if let Some(result) = cache[index] {
            result
        } else {
            let result = 1 + orbits(parent, cache, parent[index]);
            cache[index] = Some(result);
            result
        }
    }

    let cache = &mut vec![None; input.len()];
    cache[0] = Some(0);
    cache[1] = Some(0);
    (0..input.len()).map(|index| orbits(input, cache, index)).sum()
}

/// Trace Santa's path all the way to the root COM object keeping track of distance. Then
/// trace our path to the root. As soon as we encounter a non-zero distance then we've hit
/// the first common ancestor and can calculate the required transfers.
pub fn part2(input: &[usize]) -> u16 {
    let mut distance = vec![0_u16; input.len()];
    let mut index = 2; // SAN
    let mut count = 0;

    // COM = 1
    while index != 1 {
        distance[index] = count;
        index = input[index];
        count += 1;
    }

    index = 3; // YOU
    count = 0;

    while distance[index] == 0 {
        index = input[index];
        count += 1;
    }

    distance[index] + count - 2
}