aoc/year2016/
day21.rs

1//! # Scrambled Letters and Hash
2//!
3//! The forward transformations are straightforward. The trickiest reverse transformation is the
4//! rotation based on the index of the letter. First we build a lookup table of how many places to
5//! rotate right based on the letter index. This is +1 for positions 0-3 and +2 for positions 4-7.
6//!
7//! Then we invert this by mapping the transformed index to the rotation. For example position 3 is
8//! rotated right by 4 places, ending up at position 7, so the inverse lookup table to rotate left
9//! stores 4 at index 7.
10use crate::util::parse::*;
11
12const ROTATE_LETTER_RIGHT: [usize; 8] = [1, 2, 3, 4, 6, 7, 0, 1];
13const ROTATE_LETTER_LEFT: [usize; 8] = [1, 1, 6, 2, 7, 3, 0, 4];
14
15#[derive(Clone, Copy)]
16pub enum Op {
17    SwapPosition(usize, usize),
18    SwapLetter(u8, u8),
19    RotateLeft(usize),
20    RotateRight(usize),
21    RotateLetterLeft(u8),
22    RotateLetterRight(u8),
23    Reverse(usize, usize),
24    Move(usize, usize),
25}
26
27impl Op {
28    fn from(line: &str) -> Op {
29        let tokens: Vec<_> = line.split_ascii_whitespace().collect();
30        let digit = |i: usize| tokens[i].unsigned();
31        let letter = |i: usize| tokens[i].as_bytes()[0];
32
33        match tokens[0] {
34            "reverse" => Op::Reverse(digit(2), digit(4)),
35            "move" => Op::Move(digit(2), digit(5)),
36            _ => match tokens[1] {
37                "position" => Op::SwapPosition(digit(2), digit(5)),
38                "letter" => Op::SwapLetter(letter(2), letter(5)),
39                "left" => Op::RotateLeft(digit(2)),
40                "right" => Op::RotateRight(digit(2)),
41                "based" => Op::RotateLetterRight(letter(6)),
42                _ => unreachable!(),
43            },
44        }
45    }
46
47    fn transform(self, password: &mut Vec<u8>) {
48        let position = |a: u8| password.iter().position(|&b| a == b).unwrap();
49
50        match self {
51            Op::SwapPosition(first, second) => password.swap(first, second),
52            Op::SwapLetter(first, second) => {
53                let first = position(first);
54                let second = position(second);
55                password.swap(first, second);
56            }
57            Op::RotateLeft(first) => password.rotate_left(first),
58            Op::RotateRight(first) => password.rotate_right(first),
59            Op::RotateLetterLeft(first) => {
60                let first = position(first);
61                let second = ROTATE_LETTER_LEFT[first] % password.len();
62                password.rotate_left(second);
63            }
64            Op::RotateLetterRight(first) => {
65                let first = position(first);
66                let second = ROTATE_LETTER_RIGHT[first] % password.len();
67                password.rotate_right(second);
68            }
69            Op::Reverse(first, second) => password[first..=second].reverse(),
70            Op::Move(first, second) => {
71                let letter = password.remove(first);
72                password.insert(second, letter);
73            }
74        }
75    }
76
77    fn inverse(self) -> Op {
78        match self {
79            Op::RotateLeft(first) => Op::RotateRight(first),
80            Op::RotateRight(first) => Op::RotateLeft(first),
81            Op::RotateLetterLeft(first) => Op::RotateLetterRight(first),
82            Op::RotateLetterRight(first) => Op::RotateLetterLeft(first),
83            Op::Move(first, second) => Op::Move(second, first),
84            // Other operations are their own inverse.
85            other => other,
86        }
87    }
88}
89
90pub fn parse(input: &str) -> Vec<Op> {
91    input.lines().map(Op::from).collect()
92}
93
94pub fn part1(input: &[Op]) -> String {
95    scramble(input, b"abcdefgh")
96}
97
98pub fn part2(input: &[Op]) -> String {
99    unscramble(input, b"fbgdceah")
100}
101
102pub fn scramble(input: &[Op], slice: &[u8]) -> String {
103    let mut password = slice.to_vec();
104
105    for op in input {
106        op.transform(&mut password);
107    }
108
109    String::from_utf8(password).unwrap()
110}
111
112pub fn unscramble(input: &[Op], slice: &[u8]) -> String {
113    let mut password = slice.to_vec();
114
115    for op in input.iter().rev() {
116        op.inverse().transform(&mut password);
117    }
118
119    String::from_utf8(password).unwrap()
120}