require_relative 'util' # --- Day 21: Scrambled Letters and Hash --- # The computer system you're breaking into uses a weird scrambling # function to store its passwords. It shouldn't be much trouble to # create your own scrambled password so you can add it to the system; # you just have to implement the scrambler. # The scrambling function is a series of operations (the exact list is # provided in your puzzle input). Starting with the password to be # scrambled, apply each operation in succession to the string. The # individual operations behave as follows: # - swap position X with position Y means that the letters at indexes # X and Y (counting from 0) should be swapped. # - swap letter X with letter Y means that the letters X and Y should # be swapped (regardless of where they appear in the string). # - rotate left/right X steps means that the whole string should be # rotated; for example, one right rotation would turn abcd into # dabc. # - rotate based on position of letter X means that the whole string # should be rotated to the right based on the index of letter X # (counting from 0) as determined before this instruction does any # rotations. Once the index is determined, rotate the string to the # right one time, plus a number of times equal to that index, plus # one additional time if the index was at least 4. # - reverse positions X through Y means that the span of letters at # indexes X through Y (including the letters at X and Y) should be # reversed in order. # - move position X to position Y means that the letter which is at # index X should be removed from the string, then inserted such that # it ends up at index Y. # For example, suppose you start with abcde and perform the following # operations: # - swap position 4 with position 0 swaps the first and last letters, # producing the input for the next step, ebcda. # - swap letter d with letter b swaps the positions of d and b: edcba. # - reverse positions 0 through 4 causes the entire string to be # reversed, producing abcde. # - rotate left 1 step shifts all letters left one position, causing # the first letter to wrap to the end of the string: bcdea. # - move position 1 to position 4 removes the letter at position 1 # (c), then inserts it at position 4 (the end of the string): bdeac. # - move position 3 to position 0 removes the letter at position 3 # (a), then inserts it at position 0 (the front of the string): # abdec. # - rotate based on position of letter b finds the index of letter b # (1), then rotates the string right once plus a number of times # equal to that index (2): ecabd. # - rotate based on position of letter d finds the index of letter d # (4), then rotates the string right once, plus a number of times # equal to that index, plus an additional time because the index was # at least 4, for a total of 6 right rotations: decab. # After these steps, the resulting scrambled password is decab. # Now, you just need to generate a new scrambled password and you can # access the system. Given the list of scrambling operations in your # puzzle input, what is the result of scrambling abcdefgh? input = File.open('21.txt') { |f| f.readlines.map(&:chomp) } def swap_position(password, x, y) result = password.clone temp = result[x] result[x] = result[y] result[y] = temp result end def swap_letter(password, x, y) swap_position(password, password.index(x), password.index(y)) end def rotate_left(password, x) x = x % password.length left = password.slice(0, x) right = password[x..-1] right + left end def rotate_right(password, x) x = x % password.length i = password.length - x right = password[i..-1] left = password.slice(0, i) right + left end def rotate_letter(password, x) i = password.index(x) rotations = 1 + i rotations += 1 if i >= 4 rotate_right(password, rotations) end def reverse_positions(password, x, y) result = password.chars left = result.shift(x) center = result.shift(y - x + 1) right = result (left + center.reverse + right).join end def move_position(password, x, y) result = password.chars char = result.delete_at(x) result.insert(y, char) result.join end def scramble(password, line) case line when /swap position (\d) with position (\d)/ swap_position(password, $1.to_i, $2.to_i) when /swap letter (.) with letter (.)/ swap_letter(password, $1, $2) when /rotate left (\d) steps?/ rotate_left(password, $1.to_i) when /rotate right (\d) steps?/ rotate_right(password, $1.to_i) when /rotate based on position of letter (.)/ rotate_letter(password, $1) when /reverse positions (\d) through (\d)/ reverse_positions(password, $1.to_i, $2.to_i) when /move position (\d) to position (\d)/ move_position(password, $1.to_i, $2.to_i) else raise "unknown line: #{line}" end end assert(scramble('abcde', 'swap position 4 with position 0') == 'ebcda') assert(scramble('ebcda', 'swap letter d with letter b') == 'edcba') assert(scramble('edcba', 'reverse positions 0 through 4') == 'abcde') assert(scramble('abcde', 'rotate left 1 step') == 'bcdea') assert(scramble('bcdea', 'move position 1 to position 4') == 'bdeac') assert(scramble('bdeac', 'move position 3 to position 0') == 'abdec') assert(scramble('abdec', 'rotate based on position of letter b') == 'ecabd') assert(scramble('ecabd', 'rotate based on position of letter d') == 'decab') def easy(password, input) input.each do |step| password = scramble(password, step) end password end assert(easy('abcde', ['swap position 4 with position 0', 'swap letter d with letter b', 'reverse positions 0 through 4', 'rotate left 1 step', 'move position 1 to position 4', 'move position 3 to position 0', 'rotate based on position of letter b', 'rotate based on position of letter d']) == 'decab') puts "easy('abcdefgh', input): #{easy('abcdefgh', input)}" # --- Part Two --- # You scrambled the password correctly, but you discover that you # can't actually modify the password file on the system. You'll need # to un-scramble one of the existing passwords by reversing the # scrambling process. # What is the un-scrambled version of the scrambled password fbgdceah? scrambled = 'fbgdceah' # letter 0 moves by 1 to letter 1 <= left 1 # letter 1 moves by 2 to letter 3 <= left 2 # letter 2 moves by 3 to letter 5 AKA 0 <= left 3 # letter 3 moves by 4 to letter 7 AKA 2 <= left 4 # letter 4 moves by 6 to letter 10 AKA 0 <= left 6 AKA 1 # 1->1, 3->2, 0->3, 2->4, 0->1 def unrotate_letter(password, x) raise "undefined behavior: #{password.length}" unless password.length == 8 case password.index(x) when 0 then rotate_left(password, 1) when 1 then rotate_left(password, 1) when 2 then rotate_right(password, 2) when 3 then rotate_left(password, 2) when 4 then rotate_right(password, 1) when 5 then rotate_left(password, 3) when 6 then password when 7 then rotate_right(password, 4) end end def unscramble(password, line) case line when /swap position (\d) with position (\d)/ swap_position(password, $1.to_i, $2.to_i) when /swap letter (.) with letter (.)/ swap_letter(password, $1, $2) when /rotate left (\d) steps?/ rotate_right(password, $1.to_i) when /rotate right (\d) steps?/ rotate_left(password, $1.to_i) when /rotate based on position of letter (.)/ unrotate_letter(password, $1) when /reverse positions (\d) through (\d)/ reverse_positions(password, $1.to_i, $2.to_i) when /move position (\d) to position (\d)/ move_position(password, $2.to_i, $1.to_i) else raise "unknown line: #{line}" end end # assert(unscramble('decab', 'rotate based on position of letter d') == 'ecabd') # assert(unscramble('ecabd', 'rotate based on position of letter b') == 'abdec') assert(unscramble('abdec', 'move position 3 to position 0') == 'bdeac') assert(unscramble('bdeac', 'move position 1 to position 4') == 'bcdea') assert(unscramble('bcdea', 'rotate left 1 step') == 'abcde') assert(unscramble('abcde', 'reverse positions 0 through 4') == 'edcba') assert(unscramble('edcba', 'swap letter d with letter b') == 'ebcda') assert(unscramble('ebcda', 'swap position 4 with position 0') == 'abcde') def hard(password, input) input.reverse.each do |step| password = unscramble(password, step) end password end puts "hard(scrambled, input): #{hard(scrambled, input)}"