From bbf15158e7891f1d2e4192713f99d6fc0bd5716d Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sat, 11 Oct 2025 14:29:32 +0100 Subject: [PATCH 1/3] feat: improve toRoman --- index.js | 48 ++++- src/index.ts | 541 +++++++++++++++++++++++---------------------------- 2 files changed, 291 insertions(+), 298 deletions(-) diff --git a/index.js b/index.js index 93d2c8c..c3a1fcd 100644 --- a/index.js +++ b/index.js @@ -5,14 +5,15 @@ exports.range = exports.sum = exports.fromRoman = exports.toRoman = + exports.toRomans = exports.isRoman = exports.getCount = void 0; /** - * Retuns the number of times and element occurs in an array + * Returns the number of times an element occurs in an array * @param { string[] } array Array to be checked * @param { string | number } value string or number to be counted - * @return { number } Count of value in array + * @return { number } Count of value in an array */ function getCount(array, value) { let count = 0; @@ -124,14 +125,14 @@ exports.isRoman = isRoman; * @param { number } value Integer to be converted to Roman numerals * @returns { string } Roman numeral representation of the input value */ -function toRoman(value) { +function toRomans(value) { if (typeof value != "number") { // Added a conditional to check if value is a number - return new Error("Value must be a number"); + return new Error("Input must be a number"); } // Check for valid numbers if (value >= 4000 || value <= 0) { - return new Error("Value cannot be up to 4000 or less than 0"); + return new Error("Input cannot be up to 4000 or less than 0"); } let romanArray = []; // Get number digits with place value @@ -196,6 +197,43 @@ function toRoman(value) { } return romanArray.join(""); } +exports.toRomans = toRomans; +/** + * toRoman - Convert an integer to Roman numerals + * @param value Integer to convert (1–3999) + * @returns Roman numeral string + */ +function toRoman(value) { + if (!Number.isInteger(value)) { + throw new Error("Value must be of type number"); + } + if (value <= 0 || value >= 4000) { + throw new Error("Value cannot be up to 4000 or less than 0"); + } + let result = ""; + const romanMap = [ + [1000, "M"], + [900, "CM"], + [500, "D"], + [400, "CD"], + [100, "C"], + [90, "XC"], + [50, "L"], + [40, "XL"], + [10, "X"], + [9, "IX"], + [5, "V"], + [4, "IV"], + [1, "I"], + ]; + for (const [num, numeral] of romanMap) { + while (value >= num) { + result += numeral; + value -= num; + } + } + return result; +} exports.toRoman = toRoman; /** * Convert Roman numeral to integer diff --git a/src/index.ts b/src/index.ts index 9e313e6..1a76877 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,21 @@ type general = string | number; /** - * Retuns the number of times and element occurs in an array + * Returns the number of times an element occurs in an array * @param { string[] } array Array to be checked * @param { string | number } value string or number to be counted - * @return { number } Count of value in array + * @return { number } Count of value in an array */ export function getCount(array: string[], value: general): number { - let count: number = 0; + let count: number = 0; - array.forEach((item) => { - if (item === value) { - count++; - } - }); + array.forEach((item) => { + if (item === value) { + count++; + } + }); - return count; + return count; } /** @@ -24,112 +24,112 @@ export function getCount(array: string[], value: general): number { * @returns { boolean } true or false */ export function isRoman(value: string): true | Error { - if (!value) { - return new Error("Roman numeral cannot be empty"); - } - - // Input must be a string - if (typeof value != "string") { - return new Error("Roman numeral must be of type string"); - } - - value = value.toUpperCase(); - - const letters: string[] = value.split(""); - const romans = [ - ["M", 4], - ["D", 1], - ["C", 4], - ["L", 1], - ["X", 4], - ["V", 1], - ["I", 3], - ]; - const romanLetters: string[] = ["M", "D", "C", "L", "X", "V", "I"]; - - // Count rules - romans.forEach((letter) => { - let count = getCount(letters, letter[0]); - if (count && count > letter[1]) { - let error = `${letter[0]} cannot appear more than ${letter[1]} times in a value`; - return new Error(`${error}`); + if (!value) { + return new Error("Roman numeral cannot be empty"); } - }); - // Testing single digits - if (letters.length < 2) { - let letter = letters[0]; - - if (!romanLetters.includes(letter)) { - return new Error(`Invalid Roman numeral: ${letter}`); - } else { - return true; + // Input must be a string + if (typeof value != "string") { + return new Error("Roman numeral must be of type string"); } - } - // Correct letters - letters.forEach((letter, index) => { - if (!romanLetters.includes(letter)) { - return new Error(`Invalid Roman numeral: ${letter}`); - } + value = value.toUpperCase(); - let next = letters[index + 1]; + const letters: string[] = value.split(""); + const romans = [ + ["M", 4], + ["D", 1], + ["C", 4], + ["L", 1], + ["X", 4], + ["V", 1], + ["I", 3], + ]; + const romanLetters: string[] = ["M", "D", "C", "L", "X", "V", "I"]; + + // Count rules + romans.forEach((letter) => { + let count = getCount(letters, letter[0]); + if (count && count > letter[1]) { + let error = `${letter[0]} cannot appear more than ${letter[1]} times in a value`; + return new Error(`${error}`); + } + }); - // Test for D - if (letter === romanLetters[1]) { - let badNexts = romanLetters.slice(0, 2); + // Testing single digits + if (letters.length < 2) { + let letter = letters[0]; - if (badNexts.includes(next)) { - return new Error( - `Unexpected token ${next}, ${next} cannot come after ${letter}` - ); - } + if (!romanLetters.includes(letter)) { + return new Error(`Invalid Roman numeral: ${letter}`); + } else { + return true; + } } - // Test for L - if (letter === romanLetters[3]) { - let goodNexts = romanLetters.slice(4, 3); + // Correct letters + letters.forEach((letter, index) => { + if (!romanLetters.includes(letter)) { + return new Error(`Invalid Roman numeral: ${letter}`); + } - if (!goodNexts.includes(next)) { - return new Error( - `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` - ); - } - } + let next = letters[index + 1]; - // Test for X - if (letter === romanLetters[4]) { - let badNexts = romanLetters.slice(0, 2); + // Test for D + if (letter === romanLetters[1]) { + let badNexts = romanLetters.slice(0, 2); - if (badNexts.includes(next)) { - return new Error( - `Unexpected token ${next}, ${next} cannot come after ${letter}` - ); - } - } + if (badNexts.includes(next)) { + return new Error( + `Unexpected token ${next}, ${next} cannot come after ${letter}` + ); + } + } - // Test for V - if (letter === romanLetters[5]) { - let goodNexts = [romanLetters[6]]; + // Test for L + if (letter === romanLetters[3]) { + let goodNexts = romanLetters.slice(4, 3); - if (!goodNexts.includes(next)) { - return new Error(`Unexpected token ${next}, expected ${goodNexts[0]}`); - } - } + if (!goodNexts.includes(next)) { + return new Error( + `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` + ); + } + } - // Test for I - if (letter === romanLetters[6]) { - let goodNexts = romanLetters.slice(4, 3); + // Test for X + if (letter === romanLetters[4]) { + let badNexts = romanLetters.slice(0, 2); - if (!goodNexts.includes(next)) { - return new Error( - `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` - ); - } - } - }); + if (badNexts.includes(next)) { + return new Error( + `Unexpected token ${next}, ${next} cannot come after ${letter}` + ); + } + } + + // Test for V + if (letter === romanLetters[5]) { + let goodNexts = [romanLetters[6]]; + + if (!goodNexts.includes(next)) { + return new Error(`Unexpected token ${next}, expected ${goodNexts[0]}`); + } + } - return true; + // Test for I + if (letter === romanLetters[6]) { + let goodNexts = romanLetters.slice(4, 3); + + if (!goodNexts.includes(next)) { + return new Error( + `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` + ); + } + } + }); + + return true; } /** @@ -137,85 +137,40 @@ export function isRoman(value: string): true | Error { * @param { number } value Integer to be converted to Roman numerals * @returns { string } Roman numeral representation of the input value */ -export function toRoman(value: number): string | Error { - if (typeof value != "number") { - // Added a conditional to check if value is a number - return new Error("Value must be a number"); - } - - // Check for valid numbers - if (value >= 4000 || value <= 0) { - return new Error("Value cannot be up to 4000 or less than 0"); - } - - let romanArray: string[] = []; - - // Get number digits with place value - let thousand = Math.floor(value / 1000); - let hundred = Math.floor((value % 1000) / 100); - let ten = Math.floor((value % 100) / 10); - let unit = value % 10; - - // Sort thousands - for (let i = 0; i < thousand; i++) { - romanArray.push("M"); - } - - // Sort hundreds - if (hundred < 4) { - for (let i = 0; i < hundred; i++) { - romanArray.push("C"); +export function toRoman(value: number): string { + if (!Number.isInteger(value)) { + throw new Error("Value must be of type number"); } - } else if (hundred === 4) { - romanArray.push("CD"); - } else if (hundred === 5) { - romanArray.push("D"); - } else if (hundred > 5 && hundred < 9) { - romanArray.push("D"); - for (let i = 0; i < hundred - 5; i++) { - romanArray.push("C"); - } - } else { - romanArray.push("CM"); - } - - // Sort tens - if (ten < 4) { - for (let i = 0; i < ten; i++) { - romanArray.push("X"); - } - } else if (ten === 4) { - romanArray.push("XL"); - } else if (ten === 5) { - romanArray.push("L"); - } else if (ten > 5 && ten < 9) { - romanArray.push("L"); - for (let i = 0; i < ten - 5; i++) { - romanArray.push("X"); - } - } else { - romanArray.push("XC"); - } - - // Sort units - if (unit < 4) { - for (let i = 0; i < unit; i++) { - romanArray.push("I"); + + if (value <= 0 || value >= 4000) { + throw new Error("Value cannot be up to 4000 or less than 0"); } - } else if (unit === 4) { - romanArray.push("IV"); - } else if (unit === 5) { - romanArray.push("V"); - } else if (unit > 5 && unit < 9) { - romanArray.push("V"); - for (let i = 0; i < unit - 5; i++) { - romanArray.push("I"); + + let result = ""; + const romanMap: [number, string][] = [ + [1000, "M"], + [900, "CM"], + [500, "D"], + [400, "CD"], + [100, "C"], + [90, "XC"], + [50, "L"], + [40, "XL"], + [10, "X"], + [9, "IX"], + [5, "V"], + [4, "IV"], + [1, "I"], + ]; + + for (const [num, numeral] of romanMap) { + while (value >= num) { + result += numeral; + value -= num; + } } - } else { - romanArray.push("IX"); - } - return romanArray.join(""); + return result; } /** @@ -224,57 +179,57 @@ export function toRoman(value: number): string | Error { * @returns { number } Integer representation of the input value */ export function fromRoman(value: string): number | Error { - let arabNum: number = 0; - - if (isRoman(value)) { - const letters: string[] = value.split(""); - - letters.forEach((letter, index) => { - letter = letter.toUpperCase(); - - if (letter === "M") { - arabNum += 1000; - } else if (letter === "D") { - arabNum += 500; - } else if (letter === "C") { - if (letters[index + 1] === "M") { - arabNum += 900; - letters.splice(index + 1, 1); - } else if (letters[index + 1] === "D") { - arabNum += 400; - letters.splice(index + 1, 1); - } else { - arabNum += 100; - } - } else if (letter === "L") { - arabNum += 50; - } else if (letter === "X") { - if (letters[index + 1] === "C") { - arabNum += 90; - letters.splice(index + 1, 1); - } else if (letters[index + 1] === "L") { - arabNum += 40; - letters.splice(index + 1, 1); - } else { - arabNum += 10; - } - } else if (letter === "V") { - arabNum += 5; - } else if (letter === "I") { - if (letters[index + 1] === "X") { - arabNum += 9; - letters.splice(index + 1, 1); - } else if (letters[index + 1] === "V") { - arabNum += 4; - letters.splice(index + 1, 1); - } else { - arabNum += 1; - } - } - }); - } + let arabNum: number = 0; + + if (isRoman(value)) { + const letters: string[] = value.split(""); + + letters.forEach((letter, index) => { + letter = letter.toUpperCase(); + + if (letter === "M") { + arabNum += 1000; + } else if (letter === "D") { + arabNum += 500; + } else if (letter === "C") { + if (letters[index + 1] === "M") { + arabNum += 900; + letters.splice(index + 1, 1); + } else if (letters[index + 1] === "D") { + arabNum += 400; + letters.splice(index + 1, 1); + } else { + arabNum += 100; + } + } else if (letter === "L") { + arabNum += 50; + } else if (letter === "X") { + if (letters[index + 1] === "C") { + arabNum += 90; + letters.splice(index + 1, 1); + } else if (letters[index + 1] === "L") { + arabNum += 40; + letters.splice(index + 1, 1); + } else { + arabNum += 10; + } + } else if (letter === "V") { + arabNum += 5; + } else if (letter === "I") { + if (letters[index + 1] === "X") { + arabNum += 9; + letters.splice(index + 1, 1); + } else if (letters[index + 1] === "V") { + arabNum += 4; + letters.splice(index + 1, 1); + } else { + arabNum += 1; + } + } + }); + } - return arabNum; + return arabNum; } /** @@ -284,18 +239,18 @@ export function fromRoman(value: string): number | Error { * @returns { string | number } Final roman numeral */ export function sum( - expected: "number" | "roman", - ...args: string[] + expected: "number" | "roman", + ...args: string[] ): general | Error { - let sum = 0; + let sum = 0; - args.forEach((numeral) => { - if (isRoman(numeral) === true) { - sum += fromRoman(numeral) as number; - } - }); + args.forEach((numeral) => { + if (isRoman(numeral) === true) { + sum += fromRoman(numeral) as number; + } + }); - return expected === "number" ? sum : toRoman(sum); + return expected === "number" ? sum : toRoman(sum); } /** @@ -305,22 +260,22 @@ export function sum( * @returns { string | number } */ export function diff( - expected: "number" | "roman", - numerals: [string, string] + expected: "number" | "roman", + numerals: [string, string] ): general | Error { - let sum = 0; + let sum = 0; - if (numerals.length > 2) { - return new Error("Cannot subtract more than 2 numerals"); - } + if (numerals.length > 2) { + return new Error("Cannot subtract more than 2 numerals"); + } - if (isRoman(numerals[0]) && isRoman(numerals[1])) { - sum = Math.abs( - (fromRoman(numerals[0]) as number) - (fromRoman(numerals[1]) as number) - ); - } + if (isRoman(numerals[0]) && isRoman(numerals[1])) { + sum = Math.abs( + (fromRoman(numerals[0]) as number) - (fromRoman(numerals[1]) as number) + ); + } - return expected === "number" ? sum : toRoman(sum); + return expected === "number" ? sum : toRoman(sum); } /** @@ -330,63 +285,63 @@ export function diff( * @param intervals { string | number } Difference between values */ export function range( - end: general, - start: general = "I", - intervals: general = "I" + end: general, + start: general = "I", + intervals: general = "I" ): string[] | Error { - let endNum: number = 1; - let startNum: number = 1; - let diffNum: number = 1; - let ranged: string[] = []; - - // Validate end value - if (typeof end === "string") { - if (isRoman(end)) { - endNum = fromRoman(end) as number; - } - } else if (typeof end === "number") { - if (end >= 4000 || end <= 0) { - return new Error("Range has to be between 1 and 3999"); + let endNum: number = 1; + let startNum: number = 1; + let diffNum: number = 1; + let ranged: string[] = []; + + // Validate end value + if (typeof end === "string") { + if (isRoman(end)) { + endNum = fromRoman(end) as number; + } + } else if (typeof end === "number") { + if (end >= 4000 || end <= 0) { + return new Error("Range has to be between 1 and 3999"); + } + + endNum = end; + } else { + return new Error("End value must be a string or number"); } - endNum = end; - } else { - return new Error("End value must be a string or number"); - } + // Validate start value + if (start && typeof start === "string") { + if (isRoman(start)) { + startNum = fromRoman(start) as number; + } + } else if (start && typeof start === "number") { + if (start >= 4000 || start <= 0) { + return new Error("Range has to be between 1 and 3999"); + } - // Validate start value - if (start && typeof start === "string") { - if (isRoman(start)) { - startNum = fromRoman(start) as number; - } - } else if (start && typeof start === "number") { - if (start >= 4000 || start <= 0) { - return new Error("Range has to be between 1 and 3999"); + startNum = start; + } else { + return new Error("Start value must be a string or number"); } - startNum = start; - } else { - return new Error("Start value must be a string or number"); - } + // Validate interval value + if (intervals && typeof intervals === "string") { + if (isRoman(intervals)) { + diffNum = fromRoman(intervals) as number; + } + } else if (intervals && typeof intervals === "number") { + if (intervals >= 4000 || intervals <= 0) { + return new Error("Range has to be between 1 and 3999"); + } - // Validate interval value - if (intervals && typeof intervals === "string") { - if (isRoman(intervals)) { - diffNum = fromRoman(intervals) as number; - } - } else if (intervals && typeof intervals === "number") { - if (intervals >= 4000 || intervals <= 0) { - return new Error("Range has to be between 1 and 3999"); + diffNum = intervals; + } else { + return new Error("Start value must be a string or number"); } - diffNum = intervals; - } else { - return new Error("Start value must be a string or number"); - } - - for (let i = startNum; i < endNum + 1; i += diffNum) { - ranged.push(toRoman(i) as string); - } + for (let i = startNum; i < endNum + 1; i += diffNum) { + ranged.push(toRoman(i) as string); + } - return ranged; + return ranged; } From 0e78ec65297c78ce30a283b22580830de02a1daf Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sat, 11 Oct 2025 16:53:57 +0100 Subject: [PATCH 2/3] done --- index.js | 93 +++++----------------------------------------------- package.json | 1 + src/index.ts | 12 +++---- 3 files changed, 15 insertions(+), 91 deletions(-) diff --git a/index.js b/index.js index c3a1fcd..05617c3 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,6 @@ exports.range = exports.sum = exports.fromRoman = exports.toRoman = - exports.toRomans = exports.isRoman = exports.getCount = void 0; @@ -51,13 +50,15 @@ function isRoman(value) { ]; const romanLetters = ["M", "D", "C", "L", "X", "V", "I"]; // Count rules - romans.forEach((letter) => { - let count = getCount(letters, letter[0]); - if (count && count > letter[1]) { - let error = `${letter[0]} cannot appear more than ${letter[1]} times in a value`; - return new Error(`${error}`); + for (let i = 0; i < romans.length; i++) { + const [char, maxCount] = romans[i]; + const count = getCount(letters, char); + if (count && count > maxCount) { + return new Error( + `${char} cannot appear more than ${maxCount} times in a value` + ); } - }); + } // Testing single digits if (letters.length < 2) { let letter = letters[0]; @@ -125,84 +126,6 @@ exports.isRoman = isRoman; * @param { number } value Integer to be converted to Roman numerals * @returns { string } Roman numeral representation of the input value */ -function toRomans(value) { - if (typeof value != "number") { - // Added a conditional to check if value is a number - return new Error("Input must be a number"); - } - // Check for valid numbers - if (value >= 4000 || value <= 0) { - return new Error("Input cannot be up to 4000 or less than 0"); - } - let romanArray = []; - // Get number digits with place value - let thousand = Math.floor(value / 1000); - let hundred = Math.floor((value % 1000) / 100); - let ten = Math.floor((value % 100) / 10); - let unit = value % 10; - // Sort thousands - for (let i = 0; i < thousand; i++) { - romanArray.push("M"); - } - // Sort hundreds - if (hundred < 4) { - for (let i = 0; i < hundred; i++) { - romanArray.push("C"); - } - } else if (hundred === 4) { - romanArray.push("CD"); - } else if (hundred === 5) { - romanArray.push("D"); - } else if (hundred > 5 && hundred < 9) { - romanArray.push("D"); - for (let i = 0; i < hundred - 5; i++) { - romanArray.push("C"); - } - } else { - romanArray.push("CM"); - } - // Sort tens - if (ten < 4) { - for (let i = 0; i < ten; i++) { - romanArray.push("X"); - } - } else if (ten === 4) { - romanArray.push("XL"); - } else if (ten === 5) { - romanArray.push("L"); - } else if (ten > 5 && ten < 9) { - romanArray.push("L"); - for (let i = 0; i < ten - 5; i++) { - romanArray.push("X"); - } - } else { - romanArray.push("XC"); - } - // Sort units - if (unit < 4) { - for (let i = 0; i < unit; i++) { - romanArray.push("I"); - } - } else if (unit === 4) { - romanArray.push("IV"); - } else if (unit === 5) { - romanArray.push("V"); - } else if (unit > 5 && unit < 9) { - romanArray.push("V"); - for (let i = 0; i < unit - 5; i++) { - romanArray.push("I"); - } - } else { - romanArray.push("IX"); - } - return romanArray.join(""); -} -exports.toRomans = toRomans; -/** - * toRoman - Convert an integer to Roman numerals - * @param value Integer to convert (1–3999) - * @returns Roman numeral string - */ function toRoman(value) { if (!Number.isInteger(value)) { throw new Error("Value must be of type number"); diff --git a/package.json b/package.json index ea176b2..1562a51 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "tsc", "test": "jest tests/*.test.js", + "benchmark": "node benchmarks/index.bench.js", "prepare": "husky install", "format": "prettier --write \"**/*.{js,jsx,json,css,md}\"" }, diff --git a/src/index.ts b/src/index.ts index 1a76877..35c0304 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,13 +48,13 @@ export function isRoman(value: string): true | Error { const romanLetters: string[] = ["M", "D", "C", "L", "X", "V", "I"]; // Count rules - romans.forEach((letter) => { - let count = getCount(letters, letter[0]); - if (count && count > letter[1]) { - let error = `${letter[0]} cannot appear more than ${letter[1]} times in a value`; - return new Error(`${error}`); + for (let i = 0; i < romans.length; i++) { + const [char, maxCount] = romans[i]; + const count = getCount(letters, char); + if (count && count > maxCount) { + return new Error(`${char} cannot appear more than ${maxCount} times in a value`); } - }); + } // Testing single digits if (letters.length < 2) { From ab579d650b705cfed9917fb24120f0191b6bfc50 Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sat, 11 Oct 2025 16:56:16 +0100 Subject: [PATCH 3/3] chore: remove unneccessary action --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 1562a51..ea176b2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "scripts": { "build": "tsc", "test": "jest tests/*.test.js", - "benchmark": "node benchmarks/index.bench.js", "prepare": "husky install", "format": "prettier --write \"**/*.{js,jsx,json,css,md}\"" },