aoc/year2022/day13.rs
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 118 119 120 121 122 123 124 125 126 127 128
//! # Distress Signal
//!
//! One possible approach is to parse the input into a tree, then compare recursively node
//! by node. We're going to use a much faster and simpler approach by noting an observation about
//! the input data. If the sequence `10` is replaced by any single character greater than `9` then
//! we can compare the 2 packets *lexigraphically*. We'll replace all occurences of `10` with `A`
//! then compare packets character by character.
//!
//! The rules to compare 2 packets become:
//! * If both characters are the same then it's a draw, move onto the next character in each packet.
//! * If the first packet is `]` and the second packet anything else, then the first list is shorter
//! so the packets are in order.
//! * Conversely if the second packet is `]` and the first packet anything else, the packets are not
//! in order.
//! * If the first packet is an opening `[` and the second character anything else, then we're
//! comparing a number with a list, so *push* the second character back onto the list to check
//! again along with a closing `]` character.
//! * Do a similar push if the second character is an opening `[` and the first anything else.
//! * Finally compare the 2 characters by value. Since we've already covered the equal case, one
//! is guaranteed to be greater or less than the other.
struct Packet<'a> {
slice: &'a [u8],
index: usize,
extra: Vec<u8>,
}
impl Packet<'_> {
fn new(str: &str) -> Packet<'_> {
Packet { slice: str.as_bytes(), index: 0, extra: Vec::new() }
}
}
pub fn parse(input: &str) -> Vec<&str> {
input.lines().filter(|line| !line.is_empty()).collect()
}
/// Count adjacent pairs of packets that are in order.
pub fn part1(input: &[&str]) -> usize {
input
.chunks_exact(2)
.enumerate()
.map(|(i, chunk)| {
let ordered = compare(chunk[0], chunk[1]);
if ordered { i + 1 } else { 0 }
})
.sum()
}
/// Find the position of `[[2]]` and `[[6]]` in linear `O(n)` time.
///
/// One approach would be to insert `[[2]]` and `[[6]]` into the list, sort in `O(nlogn)` time,
/// then find the indices of the 2 values in `O(n)` time.
///
/// A much faster approach is to iterate over the list, comparing each packet first with `[[2]]`.
/// If the packets are in order, then increment the positions of *both* `[[2]]` and `[[6]]`,
/// since `[[2]]` is less than `[[6]]`.
///
/// If the packet and `[[2]]` are not in order, then also check against `[[6]]`, incrementing only
/// the second index if the 2 packets are in order.
///
/// This obtains the relative indices of `[[2]]` and `[[6]]` efficiently in fewer than `2n` comparisons.
pub fn part2(input: &[&str]) -> u32 {
let mut first = 1;
let mut second = 2;
for packet in input {
if compare(packet, "[[2]]") {
first += 1;
second += 1;
} else if compare(packet, "[[6]]") {
second += 1;
}
}
first * second
}
/// Compare 2 packets using the rules listed in the module description.
///
/// It's faster to use 2 temporary `vec`s to store extra characters, rather than copy each
/// packet into a mutable [`VecDeque`]. We use the [`or_else`] method on [`Option`] to check
/// in the temporary `vec` for available characters first.
///
/// [`VecDeque`]: std::collections::VecDeque
/// [`or_else`]: Option::or_else
fn compare(left: &str, right: &str) -> bool {
let mut left = Packet::new(left);
let mut right = Packet::new(right);
while let (Some(a), Some(b)) = (left.next(), right.next()) {
match (a, b) {
(a, b) if a == b => (),
(b']', _) => return true,
(_, b']') => return false,
(b'[', b) => {
right.extra.push(b']');
right.extra.push(b);
}
(a, b'[') => {
left.extra.push(b']');
left.extra.push(a);
}
(a, b) => return a < b,
}
}
unreachable!();
}
impl Iterator for Packet<'_> {
type Item = u8;
// Rely on the fact that all input is valid to avoid bounds checks
fn next(&mut self) -> Option<Self::Item> {
self.extra.pop().or_else(|| {
let (index, slice) = (self.index, self.slice);
// Replace occurences of "10" with "A"
if slice[index] == b'1' && slice[index + 1] == b'0' {
self.index += 2;
Some(b'A')
} else {
self.index += 1;
Some(slice[index])
}
})
}
}