/
d22.py
104 lines (83 loc) · 2.81 KB
/
d22.py
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#!/usr/bin/env python
"""Advent of Code 2019, Day 22"""
from itertools import repeat
from aoc19 import solve
class TECHNIQUE:
CUT = 'cut'
DEAL = 'deal with increment'
REVERSE = 'deal into new stack'
def parse(data):
commands = []
for line in data.split('\n'):
if line == TECHNIQUE.REVERSE:
commands.append((TECHNIQUE.REVERSE,))
elif line.startswith(TECHNIQUE.CUT):
cut = int(line.replace(TECHNIQUE.CUT, '').strip())
commands.append((TECHNIQUE.CUT, cut))
elif line.startswith(TECHNIQUE.DEAL):
step = int(line.replace(TECHNIQUE.DEAL, '').strip())
commands.append((TECHNIQUE.DEAL, step))
return commands
def mod_inverse(a, n):
s = 0
old_s = 1
r = n
old_r = a
while r != 0:
q = old_r // r
old_r, r = r, old_r - q * r
old_s, s = s, old_s - q * s
return old_s % n
def flattened(commands, n):
coeff = 1
shift = 0
for command in reversed(commands):
if command[0] == TECHNIQUE.REVERSE:
shift = n - 1 - shift
coeff *= -1
elif command[0] == TECHNIQUE.CUT:
shift += n + command[1] if command[1] < 0 else command[1]
elif command[0] == TECHNIQUE.DEAL:
inv = mod_inverse(command[1], n)
shift *= inv
coeff *= inv
return coeff % n, shift % n
def inverted(commands, n):
coeff = 1
shift = 0
for command in commands:
if command[0] == TECHNIQUE.REVERSE:
shift = n - 1 - shift
coeff *= -1
elif command[0] == TECHNIQUE.CUT:
shift += -command[1] if command[1] < 0 else n - command[1]
elif command[0] == TECHNIQUE.DEAL:
shift *= command[1]
coeff *= command[1]
return coeff % n, shift % n
def shuffle(commands, n=119315717514047, iters=101741582076661, index=2020):
a, b = flattened(commands, n)
if iters == 1:
return (a*index + b) % n
# Explanation:
# 1 shuffle: (a*x + b) % n
# 2 shuffles: (a^2*x + b*a + b) % n
# 3 shuffles: (a^3*x + b*a^2 + b*a + b) % n
# t shuffles: (a^t*x + b*(a^(t-1) + a^(t-2) + ... + a^ 1 + a^0)) % n
#
# (a^t) % n can be efficiently computed using pow(a, t, n).
#
# The series is a geometric series which can be rewritten as:
# geom(b, a, t) = b * (1 - a^t) / (1 - a)
#
# But under modulo, division is the modular inverse.
# geom(b, a, t) % n = (b * (1 - a^t) * mod_inverse(1 - a, n)) % n
#
# Putting it all together:
a_t = pow(a, iters, n)
return (a_t * index + b * (1 - a_t) * mod_inverse(1 - a, n)) % n
def unshuffle(commands, n=10007, card=2019):
a, b = inverted(commands, n)
return (a*card + b) % n
if __name__ == "__main__":
solve(22, parse, unshuffle, shuffle)