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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
pub fn new_nums(max_n: u8) -> Vec<u8> {
    (0..(max_n as usize) + 1).map(|x| x as u8).collect()
}

/// To achieve this, begin with a *list* of numbers from `0` to `255`, a
/// *current position* which begins at `0` (the first element in the list),
/// a *skip size* (which starts at `0`), and a sequence of *lengths* (your
/// puzzle input). Then, for each length:
///
/// -   *Reverse* the order of that *length* of elements in the *list*,
///     starting with the element at the *current position*.
/// -   *Move* the *current position* forward by that *length* plus the
///     *skip size*.
/// -   *Increase* the *skip size* by one.
///
/// The *list* is circular; if the *current position* and the *length* try
/// to reverse elements beyond the end of the list, the operation reverses
/// using as many extra elements as it needs from the front of the list. If
/// the *current position* moves past the end of the list, it wraps around
/// to the front. *Lengths* larger than the size of the *list* are invalid.
///
/// Here's an example using a smaller list:
///
/// Suppose we instead only had a circular list containing five elements,
/// `0, 1, 2, 3, 4`, and were given input lengths of `3, 4, 1, 5`.
///
/// -   The list begins as `[0] 1 2 3 4` (where square brackets indicate the
///     *current position*).
/// -   The first length, `3`, selects `([0] 1 2) 3 4` (where parentheses
///     indicate the sublist to be reversed).
/// -   After reversing that section (`0 1 2` into `2 1 0`), we get
///     `([2] 1 0) 3 4`.
/// -   Then, the *current position* moves forward by the *length*, `3`,
///     plus the *skip size*, 0: `2 1 0 [3] 4`. Finally, the *skip size*
///     increases to `1`.
///
/// <!-- -->
///
/// -   The second length, `4`, selects a section which wraps:
///     `2 1) 0 ([3] 4`.
/// -   The sublist `3 4 2 1` is reversed to form `1 2 4 3`:
///     `4 3) 0 ([1] 2`.
/// -   The *current position* moves forward by the *length* plus the *skip
///     size*, a total of `5`, causing it not to move because it wraps
///     around: `4 3 0 [1] 2`. The *skip size* increases to `2`.
///
/// <!-- -->
///
/// -   The third length, `1`, selects a sublist of a single element, and so
///     reversing it has no effect.
/// -   The *current position* moves forward by the *length* (`1`) plus the
///     *skip size* (`2`): `4 [3] 0 1 2`. The *skip size* increases to `3`.
///
/// <!-- -->
///
/// -   The fourth length, `5`, selects every element starting with the
///     second: `4) ([3] 0 1 2`. Reversing this sublist (`3 0 1 2 4` into
///     `4 2 1 0 3`) produces: `3) ([4] 2 1 0`.
/// -   Finally, the *current position* moves forward by `8`: `3 4 2 1 [0]`.
///     The *skip size* increases to `4`.
///
/// ```
/// # use advent_solutions::advent2017::knot_hash;
/// let mut nums = knot_hash::new_nums(4);
///
/// knot_hash::hash(&mut nums, &[3, 4, 1, 5], 1);
/// assert_eq!(nums, &[3, 4, 2, 1, 0]);
/// ```
pub fn hash(nums: &mut [u8], lengths: &[u8], rounds: usize) {
    let mut current_pos = 0;
    let mut skip_size = 0;

    for _ in 0..rounds {
        for length in lengths {
            for i in 0..length/2 {
                let ia = (current_pos + i as usize) % nums.len();
                let ib = (current_pos + (length - i - 1) as usize) % nums.len();

                nums.swap(ia, ib);
            }

            current_pos += (*length as usize) + skip_size;
            current_pos %= nums.len();

            skip_size += 1;
            skip_size %= nums.len();
        }
    }
}

/// In this example, the first two numbers in the list end up being `3` and
/// `4`; to check the process, you can multiply them together to produce
/// `12`.
///
/// ```
/// # use advent_solutions::advent2017::knot_hash;
/// let hashed = knot_hash::hash_lengths(4, &[3, 4, 1, 5], 1);
/// assert_eq!(hashed[0], 3);
/// assert_eq!(hashed[1], 4);
/// ```
///
pub fn hash_lengths(max_num: u8, lengths: &[u8], rounds: usize) -> Vec<u8> {
    let mut nums = new_nums(max_num);
    hash(&mut nums, &lengths, rounds);

    nums
}

/// The logic you've constructed forms a single *round* of the *Knot Hash*
/// algorithm; running the full thing requires many of these rounds. Some
/// input and output processing is also required.
///
/// First, from now on, your input should be taken not as a list of numbers,
/// but as a string of bytes instead. Unless otherwise specified, convert
/// characters to bytes using their [ASCII codes]. This will allow you to
/// handle arbitrary ASCII strings, and it also ensures that your input
/// lengths are never larger than `255`. For example, if you are given
/// `1,2,3`, you should convert it to the ASCII codes for each character:
/// `49,44,50,44,51`.
///
/// Once you have determined the sequence of lengths to use, add the
/// following lengths to the end of the sequence: `17, 31, 73, 47, 23`. For
/// example, if you are given `1,2,3`, your final sequence of lengths should
/// be `49,44,50,44,51,17,31,73,47,23` (the ASCII codes from the input
/// string combined with the standard length suffix values).
///
/// Second, instead of merely running one *round* like you did above, run a
/// total of `64` rounds, using the same *length* sequence in each round.
/// The *current position* and *skip size* should be preserved between
/// rounds. For example, if the previous example was your first round, you
/// would start your second round with the same *length* sequence
/// (`3, 4, 1, 5, 17, 31, 73, 47, 23`, now assuming they came from ASCII
/// codes and include the suffix), but start with the previous round's
/// *current position* (`4`) and *skip size* (`4`).
///
/// Once the rounds are complete, you will be left with the numbers from `0`
/// to `255` in some order, called the *sparse hash*. Your next task is to
/// reduce these to a list of only `16` numbers called the *dense hash*. To
/// do this, use numeric bitwise [XOR] to combine each consecutive block of
/// `16` numbers in the sparse hash (there are `16` such blocks in a list of
/// `256` numbers). So, the first element in the dense hash is the first
/// sixteen elements of the sparse hash XOR'd together, the second element
/// in the dense hash is the second sixteen elements of the sparse hash
/// XOR'd together, etc.
///
/// For example, if the first sixteen elements of your sparse hash are as
/// shown below, and the XOR operator is `^`, you would calculate the first
/// output number like this:
///
/// ```text
/// 65 ^ 27 ^ 9 ^ 1 ^ 4 ^ 3 ^ 40 ^ 50 ^ 91 ^ 7 ^ 6 ^ 0 ^ 2 ^ 5 ^ 68 ^ 22 = 64
/// ```
///
/// Perform this operation on each of the sixteen blocks of sixteen numbers
/// in your sparse hash to determine the sixteen numbers in your dense hash.
///
///   [ASCII codes]: https://en.wikipedia.org/wiki/ASCII#Printable_characters
///   [XOR]: https://en.wikipedia.org/wiki/Bitwise_operation#XOR
pub fn hash_str(input: &str, rounds: usize) -> Vec<u8> {
    let mut lengths = input.as_bytes().to_vec();
    lengths.extend([17, 31, 73, 47, 23].iter());

    let mut nums = new_nums(255);
    hash(&mut nums, &lengths, rounds);

    nums.chunks(16)
        .map(|chunk| chunk.iter().fold(0, |state, x| state ^ x))
        .collect::<Vec<_>>()
}