Advent of Code 2016, Day 21: Scrambled Letters and Hash

#ruby #advent of code 2016

Part A

On DAy 21 we need to implement scrambling function that will rearrange characters in our input string according to given instructions. All the instructions are described on the puzzle page.

Because Ruby has all methods necessary to implement this, solution is quite simple:

data = File.readlines("21.txt", chomp: true)
input = "abcdefgh"

def scramble(string, instruction)
  input = string.dup

  if instruction =~ /swap position (\d+) with position (\d+)/
    a, b = Regexp.last_match.captures.map(&:to_i)

    input[a], input[b] = input[b], input[a]
  elsif instruction =~ /swap letter (.+?) with letter (.+?)/
    a, b = Regexp.last_match.captures

    input.tr!("#{a}#{b}", "#{b}#{a}")
  elsif instruction =~ /rotate (.+?) (\d+) step/
    direction, steps = Regexp.last_match.captures
    direction = direction == "left" ? 1 : -1

    input = input.chars.rotate(direction * steps.to_i).join
  elsif instruction =~ /rotate based on position of letter (.+?)/
    letter = Regexp.last_match.captures.first
    index = input.index(letter)
    steps = 1 + index
    steps += 1 if index >= 4

    input = input.chars.rotate(-steps).join
  elsif instruction =~ /reverse positions (\d+) through (\d+)/
    a, b = Regexp.last_match.captures.map(&:to_i)

    input[a..b] = input[a..b].reverse
  elsif instruction =~ /move position (.+?) to position (.+?)/
    a, b = Regexp.last_match.captures.map(&:to_i)

    chars = input.chars
    letter = chars.delete_at(a)
    chars.insert(b, letter)

    input = chars.join
  end

  input
end

data.each do |instruction|
  input = scramble(input, instruction)
end

puts input

Part B

In the second part we need to implement unscramble method. I was trying to implement the reverse method of scramble to do that, so actually for each instruction do the opposite and start from the last instruction, but it didn’t work. I don’t know maybe I did some mistakes or so. In the end I decided to just brute force it:

@data = File.readlines("21.txt", chomp: true)
input = "abc"

def scramble(string, instruction)
  input = string.dup

  if instruction =~ /swap position (\d+) with position (\d+)/
    a, b = Regexp.last_match.captures.map(&:to_i)

    input[a], input[b] = input[b], input[a]
  elsif instruction =~ /swap letter (.+?) with letter (.+?)/
    a, b = Regexp.last_match.captures

    input.tr!("#{a}#{b}", "#{b}#{a}")
  elsif instruction =~ /rotate (.+?) (\d+) step/
    direction, steps = Regexp.last_match.captures
    direction = direction == "left" ? 1 : -1

    input = input.chars.rotate(direction * steps.to_i).join
  elsif instruction =~ /rotate based on position of letter (.+?)/
    letter = Regexp.last_match.captures.first
    index = input.index(letter)
    steps = 1 + index
    steps += 1 if index >= 4

    input = input.chars.rotate(-steps).join
  elsif instruction =~ /reverse positions (\d+) through (\d+)/
    a, b = Regexp.last_match.captures.map(&:to_i)

    input[a..b] = input[a..b].reverse
  elsif instruction =~ /move position (.+?) to position (.+?)/
    a, b = Regexp.last_match.captures.map(&:to_i)

    chars = input.chars
    letter = chars.delete_at(a)
    chars.insert(b, letter)

    input = chars.join
  end

  input
end

def scramble_password(password)
  @data.each do |instruction|
    password = scramble(password, instruction)
  end

  password
end

# Brute force
input.chars.permutation.to_a.each do |permutation|
  password = permutation.join
  if scramble_password(password) == input
    puts password
    break
  end
end

So we take our input, we generate all possible permutations, we scramble it and check if we have the original string.