How to Convert Arabic Numbers to Roman Numerals with SolidJS
Last updated: Oct 24, 2024
Have you heard about the romans? Who hasn’t, right 🙂
Apparently they used their own numeric system, which, one can say, was a bit too mouthful, especially when it comes to writing. It looks like this: I, II, III, IV, V, VI and so on.
Maybe that’s one of the reasons for the mankind to adopt and use the Arabic numeric system till today. The one we all know and use from early stages in school. Yes, yes, the same 1,2,3… we all start with.
What Are We Building?
In this tutorial, we will see how to build a small app that gives the user an input for entering Arabic numbers and displays their Roman equivalent with a nice, sleek animation.
We will use SolidJS for building the UI and the good, old JavaScript for implementing the algorithm for the actual conversion. More on this later in the article.
We also will take advantage of CSS Modules and SASS when it comes to making our application a bit more eye pleasing.
TL;DR
💡If you want to skip the reading, here 💁 is the GitHub repository, and here you can see the live demo 📺.
What is SolidJS?
SolidJS is a front-end library for creating reactive user interfaces. It is still relatively new. It looks a lot like React, but they say it’s simpler and faster. It got my attention recently, so I’ve decided to take a deeper look and educate myself. And, of course, share my experience with you here.
The Project
Our application is really simple. It has just a few dependencies and contains only several components. Let me walk you briefly through them.
Dependencies
{
"devDependencies": {
"vite": "^4.1.1",
"vite-plugin-solid": "^2.5.0"
},
"dependencies": {
"@motionone/solid": "^10.15.5",
"@solid-primitives/keyed": "^1.1.8",
"sass": "^1.58.3",
"solid-js": "^1.6.10"
}
}
Except the obvious dependency - solid-js , I’ve only installed sass, @motionone/solid and @solid-primitives/keyed libraries. We will use these for the styling and the animations. Vite related packages come with the SolidJS app installation, which means that when you run:
npx degit solidjs/templates/js my-app
This will install all you need to initially run your new SolidJS app.
Project Structure
Maybe folks with React experience will see immediately how much this looks like a regular React application. We have the same file/folders organization. And yes, we can use JSX with Solid as well.
After seeing the ‘new’ Solid application structure, let’s dive into the basic components that come from the library and that we need in order to build the UI.
Components
Components in Solid are, surprise-surprise 😲, regular JavaScript functions. A Solid app is composed of such functions. And, same way as in React, they support JSX. Which means we can write functions that produce DOM elements. For example, here is how it looks our Logo component:
import styles from './Logo.module.scss';
import gameLogo from '../../../assets/logo.png';
export function Logo() {
return (
<div className={styles.logo}>
<img src={gameLogo} alt="logo" />
</div>
);
}
Yes, you guessed right. This will produce HTML code like this:
Maybe some of you are already asking “C’mon Mihail, are you kidding? What’s the difference with React?” and I agree. So far everything looks pretty much the same. Let’s talk about Signals now.
Signals
Signals are the foundation of reactivity in Solid. They hold values that change over time and updates everything else that use these values. Which means that if you change a signal’s value, this change will be propagate everywhere else in your app where it’s being used.
This is something that’s missing in the React world.
When a signal is created, it gives you back two functions. A getter and setter. You use the first one to get the current value the signal contains. And the setter function is used to change that value. The syntax is the following:
const [count, setCount] = createSignal(0)
The value you pass as an argument to createSignal is the initial value that will be held by the signal, 0 in this case. This means that if you call count()without calling setCount with a different value before that, the result you will get is that zero.
Maybe now when you saw how to use it what’s its purpose, you think about its equivalent in React useState. Yes, in my case, that was the initial association that popped to my mind. The the so called signals are means to manage the state in a Solid application. As it gives you an easy way for accessing and changing it.
The Algorithm
There are multiple implementations of this algorithm. And in many different programming languages. Only a google search can say how many 🤓.
Our implementation is done in JavaScript. I keep the file containing the algorithm separated, in a directory called lib. This approach keeps our hands untied when it comes to replacing the UI with say one done with different UI library. Or maybe use it in completely different context. I.e. the frontend and the backend of our application are totally decoupled.
Let’s first walk through the algorithm itself and then we can discuss some improvements we could do.
Algorithm Steps
First this first, let me layout the code here, so that it’s easier for you to follow:
export const convertArabicToRoman = function (num) {
const rules = {
"M": 1000,
"CM": 900,
"D": 500,
"CD": 400,
"C": 100,
"XC": 90,
"L": 50,
"XL": 40,
"XXX": 30,
"XX": 20,
"X": 10,
"IX": 9,
"V": 5,
"IV": 4,
"I": 1
}
let res = "";
const romans = Object.keys(rules);
for (let i = 0; i < romans.length; ++i) {
const val = rules[romans[i]];
while (num >= val) {
num -= val;
res += romans[i];
}
}
return res;
};
Next, let’s take a look at the rules that define how the roman numerals are created.
These rules allow you to write down any number:
Keeping in mind these rules, here are few examples:
Let’s go through our solution now step by step: 🎢.
After defining the algorithm steps, let’s run a specific example through them and it will get clearer.
Example
OK, let’s take a random number and pass it to our algorithm. Say number 1293. We will skip the preparation steps and go straight to where the real magic happens. Which mean we start with getting the romans numerals which are the keys of our key-value data structure:
const romans = Object.keys(rules); // ["M", "CM", "D", "CD", "C", "XC",
// "L", "XL", "XXX", "XX", "X", "IX", "V", "IV", "I"]
This results in an array holding the roman representations we have there. That allows us to iterate over them via the for- loop and access each of them on every cycle.
Then, having access to each roman numeral from this array, what we do is getting its value:
const val = rules[romans[i]]; // first cycle this will give 1000,
// 900 in the second, and so on
So we have number 1293 as our input, we named it num. In the inner while- loop we compare the input with the currently selected value (from the rules data structure) and while it’s bigger or equal, we subtract it from our input value. And then concatenate the roman letter to our string result. In our example that would mean the following:
Is 1293 >= 1000 > yes => num = 1293 - 1000 = 293 and res = 'M'
Then we keep iterating with the result value from the previous iteration.
Is 293 >= 1000 => no => 293 >= 900 => no => 293 >= 500 => no
=> 293 >= 400 => no => 293 >= 100 => yes => 293 - 100 = 193
and res = 'MC'
Is 193 >= 100 => yes => 193 - 100 = 93 and res = 'MCC'
Is 93 >= 100 => no => 93 >= 90 => yes => 93 - 90 = 3 and res = 'MCCXC'
Is 3 >= 90 => no => 3 >= 50 => no => 3 >= 40 => no => 3 >= 30 => no => 3 >= 20
=> no => 3 >= 10 => no => 3 >= 9 => no => 3 >= 5 => no => 3 >= 4 => no => 3 >= 1
=> yes => 3 - 1 = 2 and res = 'MCCXCI'
Is 2 >= 1 => yes => 2 - 1 = 1 and res = 'MCCXCII'
and lastly
Is 1 >= 1 => yes => 1 - 1 = 0 and res = 'MCCXCIII' is the final result! 🎉
Improvements
One thing is important to be mentioned here. If we use the algorithm directly as it is, it could consume whatever number you pass to it. But, keeping in mind the rules mentioned above, the result string could get really long. This could break the UI or at very least it would make it ugly and unusable for writing.
This is the reason why some implementations are talking about adding more letters to the rules. As shown on the table below, we could have letters signifying bigger numbers. Adding this to the algorithm would significantly reduce the length of the result string when a bigger number is added.
For example, with our current implementation, if we enter 1 000 000, the result would be 1000 times the letter M. You can imagine it, it’s a long string of Ms, such as MMMMMMMM… But, if we introduce the addition letters for the bigger numbers, this will become just the letter M with a line above, as it is shown in the table below.
Conclusion
Yet another learning-by-sharing session ends here 🎉.
We achieved two main goals in this tutorial. First we touched gently a relatively new player on the front-end libraries market - SolidJS. We got familiar with its basic elements. We figured out how to use them in order to quickly build a decent user interface. And we’ve manage to use this UI to show the workings of our algorithm.
And second, the algorithm itself. We saw how can we implement it in JavaScript. We also know now what are the limitations of this approach, and what would be possible improvements. For example, add more letters for signifying the bigger numbers. This could easily remove our top limit. And instead of 4999 we could go unlimited.
Last thing I would like to say here is, as always, thank you for reading 🙏🏻, I hope it was fun and interesting for you, as it was for me writing this.
References:
← Go home