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
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.