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
//! # Alchemical Reduction
//!
//! ## Part One
//!
//! This problem is similar to checking if a parentheses expression is balanced or not.
//! We use a similar approach, maintaining a stack of unreacted polymer units. Each unit from the
//! polymer is compared to the head of the stack using bitwise logic. Lowercase and uppercase ASCII
//! codes for the same lettter are always are 32 apart, which can be checked very quickly using
//! bitwise XOR. For example:
//!
//! ```none
//!         A = 65 = 01000001
//!         a = 97 = 01100001
//!     A ^ a = 32 = 00100000
//! ```
//!
//! If two units are the same type but opposite polarity then they are popped from the stack.
//!
//! ## Part Two
//!
//! An important optimization is to use the already reacted polymer from part one. This is
//! approximately 20% of the size of the raw input. Then this smaller polymer is filtered
//! further for each of the 26 kinds of unit.
pub fn parse(input: &str) -> Vec<u8> {
    collapse(input.trim().bytes())
}

pub fn part1(input: &[u8]) -> usize {
    input.len()
}

pub fn part2(input: &[u8]) -> usize {
    (b'a'..=b'z')
        .map(|kind| collapse(input.iter().copied().filter(|&b| b | 32 != kind)).len())
        .min()
        .unwrap()
}

fn collapse(polymer: impl Iterator<Item = u8>) -> Vec<u8> {
    // It's faster to keep the head of the stack in a dedicated variable. 0 is used as a special
    // sentinel kind to indicate an empty stack as it will never match with any unit kind.
    let mut head = 0;
    let mut stack = Vec::with_capacity(10_000);

    for unit in polymer {
        // Uppercase and lowercase ASCII are always 32 apart.
        if head ^ unit == 32 {
            // The head reacts with the unit to annihilate each other so replace with the next unit
            // from the stack.
            head = stack.pop().unwrap_or(0);
        } else {
            // Don't push sentinel values.
            if head != 0 {
                stack.push(head);
            }
            head = unit;
        }
    }

    if head != 0 {
        stack.push(head);
    }

    stack
}