-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstring-number.js
More file actions
298 lines (260 loc) · 8.95 KB
/
string-number.js
File metadata and controls
298 lines (260 loc) · 8.95 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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
'use strict';
/**
* StringNumber is a class that will be able to convert strings to number
* and perform mathematical operations, and return a string value as a result.
*/
class StringNumber {
/**
* This function is for string numbers smaller than 54 bits, or 16 digits.
*
* Take an incoming list of values, convert the values and remove ineligible values;
* sum those values and return the string form of the sum to the caller.
*
* @param {Array[string | number]} terms are incoming list of values that could be of type any
* @return {string} the sum of the incoming value that is converted back into a string
*/
additionSmInt(terms = []) {
/**
* Convert all incoming terms into Number
*/
const numberTerms = this.parseStringToNumber(terms);
/**
* Add up all eligible Number terms
*/
const sumOfTerms = numberTerms.reduce((accumulator, currentVal) => {
return accumulator + currentVal;
})
/**
* Convert the result back to a string
*/
return sumOfTerms.toString();
}
/**
* Attempt to convert incoming terms into numbers, and filter out NaN values
* @param {Array[string | number]} terms are incoming values that could possibly be of type any
* @return {Array[Number]} An array of numbers after conversion and filtering
*/
parseStringToNumber(terms = []) {
/**
* if there's no incoming input, return an empty array without further processing
*/
if (terms.length === 0) return [];
return terms
.map((term) => {
return Number(term);
})
.filter((numberTerm) => {
return !Number.isNaN(numberTerm);
});
}
/**
* This function is for string numbers greater than 54 bits, or 16 digits.
*
* Take an incoming list of values, filter the list for strings without digits,
* sum those values and return the string form of the sum to the caller.
*
* @param {Array[string | number]} terms are incoming list of values that could be of type any
* @return {string} the sum of the incoming value that is converted back into a string
*/
additionLgInt(terms = []) {
/**
* filter out all non string or number values, this will allow support
* for a mix of string > 54 bits and numbers < 54 bits
*/
const regex = new RegExp(/\D/g);
const stringTerms = terms.filter((term) => {
if (typeof term === 'number') {
return true
} else if (typeof term === 'string') {
const containsNonDigits = regex.test(term);
return !containsNonDigits;
}
return false;
});
/**
* If there's only one element left after filter, then there's
* nothing to sum, just return that value;
*/
if (stringTerms.length === 1) {
const term = stringTerms.pop();
/**
* if it's a number, return the string version,
* if it's a string already, just return that.
*/
return term.toString() || term;
}
/**
* Add up all the string number values from the right to left
*/
const sumOfTerms = stringTerms.reduce((accumulator, currentVal) => {
let currentValueInStr;
let accumulatorInStr;
// convert to string if it's a number so that we can get the length
if (typeof currentVal === 'number') {
currentValueInStr = currentVal.toString();
} else {
currentValueInStr = currentVal;
}
if (typeof accumulator === 'number') {
accumulatorInStr = accumulator.toString();
} else {
accumulatorInStr = accumulator;
}
let currentValIndex = currentValueInStr.length - 1;
let accumulatorIndex = accumulatorInStr.length - 1;
let sum = '';
let carryOver = false;
while (currentValIndex >= 0 || accumulatorIndex >= 0) {
// Add digit from right to left
const currentStr = currentValueInStr[currentValIndex];
const accumulatorStr = accumulatorInStr[accumulatorIndex];
let currentDigit;
let accumulatorDigit;
if (currentStr !== undefined) {
currentDigit = Number(currentStr);
}
if (accumulatorStr !== undefined) {
accumulatorDigit = Number(accumulatorStr);
}
if (!!currentDigit && !!accumulatorDigit) {
const accumulatorDigit = Number(accumulatorStr);
let sumOfColumn = currentDigit + accumulatorDigit;
if (!!carryOver) {
sumOfColumn++;
carryOver = false;
}
if (sumOfColumn >= 10) {
carryOver = true;
sumOfColumn = sumOfColumn - 10;
}
sum = `${sumOfColumn.toString()}${sum}`;
} else {
let digitToAdd = currentDigit || accumulatorDigit;
if (!!carryOver) {
digitToAdd++;
carryOver = false;
}
sum = `${digitToAdd.toString()}${sum}`;
}
currentValIndex--;
accumulatorIndex--;
}
return sum;
})
return sumOfTerms;
}
/**
* Take an incoming list of terms, sum the values and return its string form
* @param {Array<string | number>} terms are incoming values that could possibly be of type any
* @return string form of the sum of the incoming list
*/
addition(terms = []) {
/**
* If incoming terms is not a list or has a length of 0,
* that means there's nothing to add,
* and the result will be 0
*/
if (!Array.isArray(terms) || terms.length === 0) return '0';
/**
* If there's only one element in the incoming list,
* assume that the sum of that list will be the first element
* regardless of type
*/
if (terms.length === 1) return terms.pop();
const hasLargeInt = this.containsLargeInt(terms);
if (hasLargeInt) {
return this.additionLgInt(terms);
} else {
return this.additionSmInt(terms);
}
}
/**
* Controller to determine if incoming list contains a large integer
* @param {Array<number | string>} terms are incoming list of values that could be of type any
* @boolean to determine if list contains a large int that is greater than 54 bits
*/
containsLargeInt(terms = []) {
if (!Array.isArray(terms) || terms.length === 0) return false;
let hasLargeInt = false;
terms.forEach((term) => {
if (term > Number.MAX_SAFE_INTEGER) {
hasLargeInt = true;
return;
}
});
return hasLargeInt;
}
/**
* Take an incoming list of terms, multiply the values and return its string form
* @param {Array<string | number>} terms are incoming values that could possibly be of type any
* @return string form of the product of the incoming list
*/
multiply(terms = []) {
/**
* If incoming terms is not a list or has a length of 0,
* that means there's nothing to multiply,
* and the result will be 0
*/
if (!Array.isArray(terms) || terms.length === 0) return '0';
/**
* If there's only one element in the incoming list,
* assume that the product of that list will be the first element
* regardless of type
*/
if (terms.length === 1) return terms.pop();
const hasLargeInt = this.containsLargeInt(terms);
if (hasLargeInt) {
return terms.reduce((accumulator, currentVal) => {
const bigIntToAdd = [];
for (let i = currentVal; i > 0; i--) {
// ensure accumulator is always a string
bigIntToAdd.push(accumulator.toString() || accumulator);
}
return this.additionLgInt(bigIntToAdd);
});
} else {
return this.multiplySmInt(terms);
}
}
/**
* This function is for string numbers smaller than 54 bits, or 16 digits.
*
* Take an incoming list of values, convert the values and remove ineligible values;
* multiply those values and return the string form of the product to the caller.
*
* @param {Array[string | number]} terms are incoming list of values that could be of type any
* @return {string} the product of the incoming value that is converted back into a string
*/
multiplySmInt(terms = []) {
/**
* Convert all incoming terms into Number
*/
const numberTerms = this.parseStringToNumber(terms);
/**
* Multiply all eligible Number terms
*/
const productOfTerms = numberTerms.reduce((accumulator, currentVal) => {
return accumulator * currentVal;
})
/**
* If the product happens to go above MAX_SAFE_INTEGER, run it through the
* addition function to get the correct result, otherwise return the string
* value of the product
*/
if (productOfTerms < Number.MAX_SAFE_INTEGER) {
/**
* Convert the result back to a string
*/
return productOfTerms.toString();
} else {
return terms.reduce((accumulator, currentVal) => {
const bigIntToAdd = [];
for (let i = currentVal; i > 0; i--) {
bigIntToAdd.push(accumulator.toString() || accumulator);
}
return this.additionLgInt(bigIntToAdd);
})
}
}
}
module.exports = StringNumber;