aoc/year2022/
day13.rs

1//! # Distress Signal
2//!
3//! One possible approach is to parse the input into a tree, then compare recursively node
4//! by node. We're going to use a much faster and simpler approach by noting an observation about
5//! the input data. If the sequence `10` is replaced by any single character greater than `9` then
6//! we can compare the 2 packets *lexigraphically*. We'll replace all occurences of `10` with `A`
7//! then compare packets character by character.
8//!
9//! The rules to compare 2 packets become:
10//! * If both characters are the same then it's a draw, move onto the next character in each packet.
11//! * If the first packet is `]` and the second packet anything else, then the first list is shorter
12//!   so the packets are in order.
13//! * Conversely if the second packet is `]` and the first packet anything else, the packets are not
14//!   in order.
15//! * If the first packet is an opening `[` and the second character anything else, then we're
16//!   comparing a number with a list, so *push* the second character back onto the list to check
17//!   again along with a closing `]` character.
18//! * Do a similar push if the second character is an opening `[` and the first anything else.
19//! * Finally compare the 2 characters by value. Since we've already covered the equal case, one
20//!   is guaranteed to be greater or less than the other.
21struct Packet<'a> {
22    slice: &'a [u8],
23    index: usize,
24    extra: Vec<u8>,
25}
26
27impl Packet<'_> {
28    fn new(str: &str) -> Packet<'_> {
29        Packet { slice: str.as_bytes(), index: 0, extra: Vec::new() }
30    }
31}
32
33pub fn parse(input: &str) -> Vec<&str> {
34    input.lines().filter(|line| !line.is_empty()).collect()
35}
36
37/// Count adjacent pairs of packets that are in order.
38pub fn part1(input: &[&str]) -> usize {
39    input
40        .chunks_exact(2)
41        .enumerate()
42        .map(|(i, chunk)| {
43            let ordered = compare(chunk[0], chunk[1]);
44            if ordered { i + 1 } else { 0 }
45        })
46        .sum()
47}
48
49/// Find the position of `[[2]]` and `[[6]]` in linear `O(n)` time.
50///
51/// One approach would be to insert `[[2]]` and `[[6]]` into the list, sort in `O(nlogn)` time,
52/// then find the indices of the 2 values in `O(n)` time.
53///
54/// A much faster approach is to iterate over the list, comparing each packet first with `[[2]]`.
55/// If the packets are in order, then increment the positions of *both* `[[2]]` and `[[6]]`,
56/// since `[[2]]` is less than `[[6]]`.
57///
58/// If the packet and `[[2]]` are not in order, then also check against `[[6]]`, incrementing only
59/// the second index if the 2 packets are in order.
60///
61/// This obtains the relative indices of `[[2]]` and `[[6]]` efficiently in fewer than `2n` comparisons.
62pub fn part2(input: &[&str]) -> u32 {
63    let mut first = 1;
64    let mut second = 2;
65
66    for packet in input {
67        if compare(packet, "[[2]]") {
68            first += 1;
69            second += 1;
70        } else if compare(packet, "[[6]]") {
71            second += 1;
72        }
73    }
74
75    first * second
76}
77
78/// Compare 2 packets using the rules listed in the module description.
79///
80/// It's faster to use 2 temporary `vec`s to store extra characters, rather than copy each
81/// packet into a mutable [`VecDeque`]. We use the [`or_else`] method on [`Option`] to check
82/// in the temporary `vec` for available characters first.
83///
84/// [`VecDeque`]: std::collections::VecDeque
85/// [`or_else`]: Option::or_else
86fn compare(left: &str, right: &str) -> bool {
87    let mut left = Packet::new(left);
88    let mut right = Packet::new(right);
89
90    while let (Some(a), Some(b)) = (left.next(), right.next()) {
91        match (a, b) {
92            (a, b) if a == b => (),
93            (b']', _) => return true,
94            (_, b']') => return false,
95            (b'[', b) => {
96                right.extra.push(b']');
97                right.extra.push(b);
98            }
99            (a, b'[') => {
100                left.extra.push(b']');
101                left.extra.push(a);
102            }
103            (a, b) => return a < b,
104        }
105    }
106
107    unreachable!();
108}
109
110impl Iterator for Packet<'_> {
111    type Item = u8;
112
113    // Rely on the fact that all input is valid to avoid bounds checks
114    fn next(&mut self) -> Option<Self::Item> {
115        self.extra.pop().or_else(|| {
116            let (index, slice) = (self.index, self.slice);
117
118            // Replace occurences of "10" with "A"
119            if slice[index] == b'1' && slice[index + 1] == b'0' {
120                self.index += 2;
121                Some(b'A')
122            } else {
123                self.index += 1;
124                Some(slice[index])
125            }
126        })
127    }
128}