-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCaptainPotatoIntToRomanNumeral.js
More file actions
248 lines (218 loc) · 7.42 KB
/
CaptainPotatoIntToRomanNumeral.js
File metadata and controls
248 lines (218 loc) · 7.42 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
const invalidtovalidmap = {
I: 'IV',
V: 'VX',
X: 'XL',
C: 'CD',
D: 'DM',
};
const nexthighest = {
I: 'V',
X: 'L',
C: 'D',
};
const replacenexthighest = {
I: 'IX',
C: 'CM',
X: 'XC',
C: 'CM',
};
const CHUNKSIZE = 100; //not for thread input, for output
const THREADCOUNT = 4;
const LASTTHREAD = THREADCOUNT - 1;
const arethreadsexecuted = new Array(THREADCOUNT);
let workers = new Array(THREADCOUNT);
const setupAreThreadsExecuted = () => {
for (let i = 0; i < THREADCOUNT; ++i) {
arethreadsexecuted[i] = false;
}
}
const setupNewWorkersArray = () => {
for (let i = 0; i < THREADCOUNT; ++i) {
workers[i] = new Worker('worker.js');
}
}
const stallUntilThreadsExecuted = (cb) => {
console.log('stalling');
const done = arethreadsexecuted.every((threadis) => {
return threadis;
});
if (done) {
cb();
}
else {
setTimeout(stallUntilThreadsExecuted.bind(this, cb), 50);
}
}
const workerReturn = function(e) {
//scan chunk the data with constant offset option
const int8arr = new Int8Array(e.data.buffer);
let i = 0;
while (i < (int8arr.length - CHUNKSIZE)) {
const arr = []
for (const moretodo = CHUNKSIZE + i; i < moretodo; ++i) {
arr[i] = String.fromCharCode(int8arr[i]);
}
output.appendChild(document.createTextNode(arr.join("")));
}
const arr = [];
for (;i < int8arr.length; ++i) {
arr[i] = String.fromCharCode(int8arr[i]);
}
output.appendChild(document.createTextNode(arr.join("")));
arethreadsexecuted[e.data.id] = true;
}
/* PERFORMANCE NOTES - nearly 100% of the execution time is spent
generating and concatting and transmitting strings of Ms to
the main thread. */
//"compile" an int to a Roman Numeral!
//I thought of this after working on
//a compiler for a while. Previously
//the problem stumped me. The Realization
//was that an invalid roman numeral is
//useful as an intermediate to solving
//the problem. I just wanted to solve
//it as efficiently as possible, and
//disregarded loads of branching as very poor
//although that is a fine solution as well.
//I new how to generate this intermediate
//but couldn't see the use until I compilered.
const intToRomanNumeral = (str) => {
//setup global pre allocated state
setupAreThreadsExecuted();
setupNewWorkersArray();
//make new output node
var cNode = output.cloneNode(false);
output.parentNode.replaceChild(cNode ,output);
//if str is all digits
if (/^\d+$/.test(str)) {
//generate the intermediate (invalid - doesn't follow rule)
let inputnum = +str;
if (inputnum > 100000000000) { //MAX_SAFE_INTEGER: 9007199254740992
output.textContent = 'YOU MIGHT NOT HAVE ENOUGH MEMORY FOR THAT!';
return;
}
let romannumeral = [];
let numM = Math.floor(inputnum / 1000);
if (numM) {
inputnum -= numM * 1000;
}
const numD = Math.floor(inputnum / 500);
if (numD) {
inputnum -= numD * 500;
}
const numC = Math.floor(inputnum / 100);
if (numC) {
inputnum -= numC * 100;
}
const numL = Math.floor(inputnum / 50);
if (numL) {
inputnum -= numL * 50;
}
const numX = Math.floor(inputnum / 10);
if (numX) {
inputnum -= numX * 10;
}
const numV = Math.floor(inputnum / 5);
if (numV) {
inputnum -= numV * 5;
}
//here we chunk on all the Ms to the output using web workers
//before using our new num vars to generate the intermediate
console.time('PUSH MS!');
if (numM > 3) {
const remain = numM % 4;
numM = Math.floor(numM / 4);
for (let i = 0; i < THREADCOUNT; i++) {
workers[i].onmessage = workerReturn;
}
for (let i = 0; i < LASTTHREAD; i++) {
workers[i].postMessage([numM, i]);
}
workers[LASTTHREAD].postMessage([numM+remain, LASTTHREAD]);
}
else {
for (let i = 0; i < THREADCOUNT; i++) {
arethreadsexecuted[i] = true;
}
for (let i = 0; i < numM; i++) {
romannumeral.push('M');
}
}
stallUntilThreadsExecuted(() => {
//our threads have chunked their data completely onto
//the output
console.timeEnd('PUSH MS!');
for (let i = 0; i < numD; i++) {
romannumeral.push('D');
}
for (let i = 0; i < numC; i++) {
romannumeral.push('C');
}
for (let i = 0; i < numL; i++) {
romannumeral.push('L');
}
for (let i = 0; i < numX; i++) {
romannumeral.push('X');
}
for (let i = 0; i < numV; i++) {
romannumeral.push('V');
}
for (let i = 0; i < inputnum; i++) {
romannumeral.push('I');
}
//find invalid strings in the intermediate
//generate an array of new str segments to be joined
//use our maps to replace the invalid strings with their valid forms
let validromannumeral = [];
let count = 0;
let i = 1;
let lastnumeral = romannumeral[0];
validromannumeral.push(lastnumeral);
for (;i < romannumeral.length; ++i) {
if (lastnumeral === romannumeral[i]) {
if (count === 2) {
count = 0;
validromannumeral.pop();
validromannumeral.pop();
validromannumeral.pop();
if (nexthighest[romannumeral[i]]) {
if (validromannumeral[validromannumeral.length-1] === nexthighest[romannumeral[i]]) {
validromannumeral.pop();
validromannumeral.push(replacenexthighest[romannumeral[i]]);
}
else {
validromannumeral.push(invalidtovalidmap[romannumeral[i]]);
}
}
else {
validromannumeral.push(invalidtovalidmap[romannumeral[i]]);
}
}
else {
++count;
validromannumeral.push(romannumeral[i]);
}
}
else {
lastnumeral = romannumeral[i];
count = 0;
validromannumeral.push(romannumeral[i]);
}
}
//we're done, tack the little symbols on the end
output.appendChild(document.createTextNode(validromannumeral.join("")));
});
}
else {
//input had other chars than digits
output.textContent = 'INVALID INTEGER';
return;
}
}
window.onload = () => {
const input = document.getElementById('input');
const output = document.getElementById('output');
input.oninput = () => {
intToRomanNumeral(input.value);
}
}