-
Notifications
You must be signed in to change notification settings - Fork 0
/
day22.js
143 lines (124 loc) · 3.66 KB
/
day22.js
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
function createDeck(size) {
return Array.from(Array(size).keys());
}
function newStack(arr) {
return [...arr].reverse();
}
function cutN(arr, n) {
return n > 0
? [...arr.slice(n), ...arr.slice(0, n)]
: [...arr.slice(n), ...arr.slice(0, n)];
}
function incrementN(arr, n) {
const newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr[(i * n) % arr.length] = arr[i];
}
return newArr;
}
const methods = {
"deal into new stack": newStack,
"deal with increment": incrementN,
cut: cutN
};
function deal(input, size = 10) {
return input.split("\n").reduce((arr, instruction) => {
const methodName = Object.keys(methods).find(name =>
instruction.startsWith(name)
);
const method = methods[methodName];
const input = instruction
.slice(methodName.length + 1)
.split(" ")
.map(n => parseInt(n, 10));
return method(arr, ...input);
}, createDeck(size));
}
// part 2
// % in js is remainder not proper modulo so this makes it work for negative values
function mod(n, m) {
return ((n % m) + m) % m;
}
function newStack([offset, increment], size) {
const newIncrement = mod(increment * -1n, size);
return [mod(offset + newIncrement, size), newIncrement];
}
function cutN(n, [offset, increment], size) {
return [mod(offset + increment * n, size), increment];
}
function incrementN(n, [offset, increment], size) {
// fast modular inverse because all of our deck sizes are prime
return [offset, mod(increment * modInv(n, size), size)];
}
// xgcd function stolen from https://stackoverflow.com/a/26986636 and modified to work with big int
function xgcd(a, b) {
if (b === 0n) {
return [1n, 0n, a];
}
let temp = xgcd(b, a % b);
let x = temp[0];
let y = temp[1];
let d = temp[2];
return [y, x - y * BigInt(Math.floor(Number(a) / Number(b))), d];
}
function modInv(a, b) {
// we could use this instead because our b is prime but I wanted to test this against
// the size 10 deck examples
// modPow(a, b - 2n, b)
return mod(xgcd(a, b)[0], b);
}
// modPow function stolen from https://gist.github.com/krzkaczor/0bdba0ee9555659ae5fe and adapted for big int
function modPow(a, b, n) {
let result = 1n;
let x = a % n;
while (b > 0) {
const leastSignificantBit = b % 2n;
b = BigInt(Math.floor(Number(b) / 2));
if (leastSignificantBit === 1n) {
result = result * x;
result = result % n;
}
x = x * x;
x = x % n;
}
return result;
}
function repeatShuffle(n, [offset, increment], size) {
const newIncrement = modPow(increment, n, size);
const newOffset =
offset * (1n - newIncrement) * modInv(mod(1n - increment, size), size);
return [newOffset, newIncrement];
}
function deal(
input,
sizeNumber = 119315717514047,
timesNumber = 101741582076661,
offsetNumber = 2020
) {
const size = BigInt(sizeNumber);
const times = BigInt(timesNumber);
const offset = BigInt(offsetNumber);
const methods = {
"deal into new stack": newStack,
"deal with increment": incrementN,
cut: cutN
};
// nth card = offset + (increment * n) % number of cards
const result = input.split("\n").reduce(
([offset, increment], instruction) => {
const methodName = Object.keys(methods).find(name =>
instruction.startsWith(name)
);
const method = methods[methodName];
const input = instruction
.slice(methodName.length + 1)
.split(" ")
.filter(str => str)
.map(n => BigInt(parseInt(n, 10)));
return method(...input, [offset, increment], size);
},
[0n, 1n]
);
const [totalOffset, totalIncrement] = repeatShuffle(times, result, size);
return Number(mod(totalOffset + totalIncrement * offset, size));
}