Skip to main content

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(char, char),
19    RotateLeft(usize),
20    RotateRight(usize),
21    RotateLetterLeft(char),
22    RotateLetterRight(char),
23    Reverse(usize, usize),
24    Move(usize, usize),
25}
26
27impl Op {
28    fn from(line: &str) -> Self {
29        let tokens: Vec<_> = line.split_ascii_whitespace().collect();
30        let digit = |i: usize| tokens[i].unsigned();
31        let letter = |i: usize| tokens[i].chars().next().unwrap();
32
33        match (tokens[0], tokens[1]) {
34            ("reverse", "positions") => Self::Reverse(digit(2), digit(4)),
35            ("move", "position") => Self::Move(digit(2), digit(5)),
36            ("swap", "position") => Self::SwapPosition(digit(2), digit(5)),
37            ("swap", "letter") => Self::SwapLetter(letter(2), letter(5)),
38            ("rotate", "left") => Self::RotateLeft(digit(2)),
39            ("rotate", "right") => Self::RotateRight(digit(2)),
40            ("rotate", "based") => Self::RotateLetterRight(letter(6)),
41            _ => unreachable!(),
42        }
43    }
44
45    fn transform(self, password: &mut Vec<char>) {
46        let position = |a| password.iter().position(|&b| a == b).unwrap();
47
48        match self {
49            Self::SwapPosition(first, second) => password.swap(first, second),
50            Self::SwapLetter(first, second) => {
51                let first = position(first);
52                let second = position(second);
53                password.swap(first, second);
54            }
55            Self::RotateLeft(first) => password.rotate_left(first),
56            Self::RotateRight(first) => password.rotate_right(first),
57            Self::RotateLetterLeft(first) => {
58                let first = position(first);
59                let second = ROTATE_LETTER_LEFT[first] % password.len();
60                password.rotate_left(second);
61            }
62            Self::RotateLetterRight(first) => {
63                let first = position(first);
64                let second = ROTATE_LETTER_RIGHT[first] % password.len();
65                password.rotate_right(second);
66            }
67            Self::Reverse(first, second) => password[first..=second].reverse(),
68            Self::Move(first, second) => {
69                let letter = password.remove(first);
70                password.insert(second, letter);
71            }
72        }
73    }
74
75    fn inverse(self) -> Self {
76        match self {
77            Self::RotateLeft(first) => Self::RotateRight(first),
78            Self::RotateRight(first) => Self::RotateLeft(first),
79            Self::RotateLetterLeft(first) => Self::RotateLetterRight(first),
80            Self::RotateLetterRight(first) => Self::RotateLetterLeft(first),
81            Self::Move(first, second) => Self::Move(second, first),
82            // Other operations are their own inverse.
83            other => other,
84        }
85    }
86}
87
88pub fn parse(input: &str) -> Vec<Op> {
89    input.lines().map(Op::from).collect()
90}
91
92pub fn part1(input: &[Op]) -> String {
93    scramble(input, "abcdefgh")
94}
95
96pub fn part2(input: &[Op]) -> String {
97    unscramble(input, "fbgdceah")
98}
99
100pub fn scramble(input: &[Op], slice: &str) -> String {
101    let mut password = slice.chars().collect();
102
103    for op in input {
104        op.transform(&mut password);
105    }
106
107    password.iter().collect()
108}
109
110pub fn unscramble(input: &[Op], slice: &str) -> String {
111    let mut password = slice.chars().collect();
112
113    for op in input.iter().rev() {
114        op.inverse().transform(&mut password);
115    }
116
117    password.iter().collect()
118}