-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrwhite_22.py
68 lines (58 loc) · 3.07 KB
/
rwhite_22.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
from collections import deque
from itertools import count
data = open('day_22.input').read().split('\n')
def process_deck(cards):
deck = deque(cards)
for line in data:
increment = 0
if line.startswith('deal into new stack'):
deck.reverse()
elif line.startswith('deal with increment'):
increment = int(line.split()[-1])
for v in deck.copy():
deck[0] = v
deck.rotate(-increment)
elif line.startswith('cut'):
increment = int(line.split()[-1])
deck.rotate(-increment)
return deck
# Can actually replace this with my shuffle_card function from part 2, which should be faster as it just reduces to maths
print(process_deck(range(10007)).index(2019))
# So part 2 is going to be super math heavy.
# Not going to lie, had to get some hints from others in the directions to look.
# But basically we can reduce this down to some linear equations, and then the whole thing produces an LCG pseudorandom sequence
# with a repeating period equal to the number of cards in the deck.
# Source https://tailcall.net/blog/cracking-randomness-lcgs/
def get_mod_mul_inc(c0, c1, c2, num_cards):
mul = (c2 - c1) * pow(c1 - c0, -1, num_cards) % num_cards
inc = (c1 - c0 * mul) % num_cards
return num_cards, mul, inc
def super_shuffle(mul, inc, mod, card_index, shuffle_by):
# https://www.nayuki.io/page/fast-skipping-in-a-linear-congruential-generator
# This is where the LCG part comes into play, basically we've already created and LCG,
# we just need to skip ahead by some number of shuffles, which is what this function does.
return ((pow(mul, shuffle_by, (mul - 1) * mod) - 1) // (mul -1) * inc + pow(mul, shuffle_by, mod) * card_index) % mod
def shuffle_card(instructions, num_cards, card_index):
# Gets the new index for a card after running through the shuffle instructions
# each instruction can be reduced into either some addition/subtraction + modulus
# or multiplication + modulus operations.
for line in instructions:
if line.startswith('deal into new stack'):
card_index = (num_cards - card_index - 1) % num_cards
elif line.startswith('deal with increment'):
increment = int(line.split()[-1])
card_index = (card_index * increment) % num_cards
elif line.startswith('cut'):
increment = int(line.split()[-1])
card_index = (card_index - increment) % num_cards
return card_index
DECK_SIZE = 119315717514047
SHUFFLES = 101741582076661
CARD_INDEX = 2020
card_index = CARD_INDEX
# So we need at least 3 values for a card's state after shuffling to derive the multiplier and increment value needed to
# derive all future values for that index. Because of the pseudorandom nature, any index will do, but might as well stick with
# the puzzle's constant.
mod, mul, inc = get_mod_mul_inc(*[(card_index := shuffle_card(data, DECK_SIZE, card_index)) for i in range(3)], DECK_SIZE)
print(shuffle_card(data, 10007, 2019))
print(super_shuffle(mul, inc, mod, CARD_INDEX, DECK_SIZE - SHUFFLES - 1))