-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
129 lines (112 loc) · 2.85 KB
/
index.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
class RomanNumber {
#VALIDATION_REGEX = /^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/;
#MAP = {
M: 1000,
CM: 900,
D: 500,
CD: 400,
C: 100,
XC: 90,
L: 50,
XL: 40,
X: 10,
IX: 9,
V: 5,
IV: 4,
I: 1,
};
constructor(input) {
this.#validate(input);
this.data = this.#convertToDecimal(input);
}
toInt() {
return this.data;
}
toString() {
return this.#convertToRoman(this.data);
}
#convertToDecimal(numeral) {
if (typeof numeral === "number") {
return numeral;
}
let output = 0;
for (let i = 0; i < numeral.length; i++) {
// check if it's a double-letter sign.
let double = numeral.slice(i, i + 2);
if (this.#MAP.hasOwnProperty(double)) {
output += this.#MAP[double];
i++;
continue;
}
// orherwise go for a single-letter sign.
let single = numeral.slice(i, i + 1);
output += this.#MAP[single];
}
return output;
}
#convertToRoman(number) {
const map = this.#objectFlip(this.#MAP);
let output = "";
let elements = [];
let zeros = number.toString().length - 1;
// split the number into units, tens, hundreads...
for (let num of number.toString()) {
elements.push(num + "0".repeat(zeros--));
}
// concatenate the numeral
for (let element of elements) {
// zero doesn't need to be converted
if (element === "0") continue;
// if the value exists in the map just add it e.g. 1000 -> M
if (map.hasOwnProperty(element)) {
output += map[element];
continue;
}
// now let's handle more complex components like LXXX
let remaining = parseInt(element);
while (remaining > 0) {
let key = Object.keys(map)
.reverse()
.find((value) => value <= remaining);
output += map[key];
remaining -= parseInt(key);
}
}
return output;
}
#validateRomanNumeral(numeral) {
if (!this.#VALIDATION_REGEX.test(numeral)) {
throw Error("invalid input");
}
}
#objectFlip(object) {
const result = {};
Object.keys(object).forEach((key) => {
result[object[key]] = key;
});
return result;
}
#validate(input) {
// check empty values
if (input === null || input === "") {
throw Error("value required");
}
// check input is a valid int or a string
if (
typeof input === "number" &&
!Number.isInteger(input) &&
typeof input !== "stirng"
) {
throw Error("invalid input");
}
// check input is in correct range
if (Number.isInteger(input) && (input < 1 || input > 3999)) {
throw Error("invalid range");
}
// check string is a valid roman numeral
if (typeof input === "string") {
this.#validateRomanNumeral(input);
}
}
}
module.exports = RomanNumber;