-
Notifications
You must be signed in to change notification settings - Fork 2
/
21_scrambled_passwords.rb
62 lines (60 loc) · 1.73 KB
/
21_scrambled_passwords.rb
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
def apply(instructions, input, undo: false)
instructions.reduce(input.dup) { |pw, (cmd, *args)|
case cmd
when :swap_letter
# Undo == do
pw.tr(args.join, args.join.reverse)
when :swap_position
# Undo == do
i, j = args
pw[i], pw[j] = [pw[j], pw[i]]
pw
when :rotate_right
pw.chars.rotate(args[0] * (undo ? 1 : -1)).join
when :rotate_left
pw.chars.rotate(args[0] * (undo ? -1 : 1)).join
when :rotate_based
i = pw.index(args[0])
if undo
# rotate_based needs the most work to undo.
# pos shift newpos
# 0 1 1
# 1 2 3
# 2 3 5
# 3 4 7
# 4 6 2
# 5 7 4
# 6 8 6
# 7 9 0
# all odds have a clear pattern, all evens have a clear pattern...
# except 0, which we'll just special-case.
rot = i / 2 + (i % 2 == 1 || i == 0 ? 1 : 5)
else
rot = -(i + 1 + (i >= 4 ? 1 : 0))
end
pw.chars.rotate(rot).join
when :reverse_positions
# Undo == do
c = pw.chars
s, e = args
c[s..e] = c[s..e].reverse
c.join
when :move_position
from, to = undo ? args.reverse : args
c = pw.chars
ch = c.delete_at(from)
c.insert(to, ch)
c.join
else raise "Unknown command #{cmd} #{args}"
end
}
end
instructions = ARGF.each_line.map { |l|
words = l.split
# All the args are either single letters or single digits.
[words[0..1].join(?_).to_sym] + words.select { |w| w.size == 1 }.map { |w|
w.match?(/\d+/) ? Integer(w) : w
}
}
puts apply(instructions, 'abcdefgh')
puts apply(instructions.reverse, 'fbgdceah', undo: true)