In [1]:
class Scramble:
def __init__(self, password):
self.password = list(password)
def swap_positions(self, x, y):
"""The letters at indexes X and Y (counting from 0) should be swapped."""
self.password[x], self.password[y] = self.password[y], self.password[x]
def swap_letters(self, x, y):
"""The letters X and Y should be swapped (regardless of where they appear in the string)."""
x = self.password.index(x)
y = self.password.index(y)
self.swap_positions(x, y)
def rotate_by_steps(self, how, steps):
"""The whole string should be rotated; for example, one right rotation would turn abcd into dabc."""
pwd = list(self.password)
for i in range(len(self.password)):
if how == 'right':
self.password[(i + steps) % len(self.password)] = pwd[i]
else:
self.password[(i - steps) % len(self.password)] = pwd[i]
def rotate_by_position(self, x):
"""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.
"""
steps = self.password.index(x)
steps += 2 if steps >= 4 else 1
self.rotate_by_steps(how='right', steps=steps)
def reverse(self, x, y):
"""The span of letters at indexes X through Y (including the
letters at X and Y) should be reversed in order.
"""
sublist = self.password[x: y + 1]
sublist.reverse()
self.password = self.password[: x] + sublist + self.password[y + 1: ]
def move(self, x, y):
"""The letter which is at index X should be removed from the string,
then inserted such that it ends up at index Y.
"""
self.password.insert(y, self.password.pop(x))
def get_scrambled_password(self):
return ''.join(self.password)
def run(self, data):
for instruction in data:
instruction = instruction.split()
if instruction[0] == 'swap' and instruction[1] == 'position':
x = int(instruction[2])
y = int(instruction[-1])
self.swap_positions(x, y)
elif instruction[0] == 'swap' and instruction[1] == 'letter':
x = instruction[2]
y = instruction[-1]
self.swap_letters(x, y)
elif instruction[0] == 'rotate' and instruction[1] == 'based':
x = instruction[-1]
self.rotate_by_position(x)
elif instruction[0] == 'rotate':
how = instruction[1]
steps = int(instruction[2])
self.rotate_by_steps(how, steps)
elif instruction[0] == 'reverse':
x = int(instruction[2])
y = int(instruction[-1])
self.reverse(x, y)
elif instruction[0] == 'move':
x = int(instruction[2])
y = int(instruction[-1])
self.move(x, y)
In [2]:
data = [
'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'
]
scramble = Scramble('abcde')
scramble.run(data)
assert scramble.get_scrambled_password() == 'decab'
In [3]:
data = [line.strip() for line in open('data/day_21.txt', 'r')]
In [4]:
scramble = Scramble('abcdefgh')
scramble.run(data)
scramble.get_scrambled_password()
Out[4]:
In [5]:
import itertools
from tqdm import tqdm
In [6]:
def unscramble(password):
res = []
all_passwords = itertools.permutations(list(password), len(password))
for pwd in tqdm(all_passwords):
scramble = Scramble(''.join(pwd))
scramble.run(data)
if scramble.get_scrambled_password() == password:
res.append(''.join(pwd))
return res
In [7]:
data = [
'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'
]
assert 'abcde' in unscramble('decab')
In [8]:
data = [line.strip() for line in open('data/day_21.txt', 'r')]
unscramble('fbgdceah')
Out[8]: