mihail gaberov

m g

Qui docet, discit.

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 logo
SolidJS logo

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

Project Structure
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:

Logo component in the DOM
Logo component in the DOM

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

SolidJS Signal
SolidJS Signal

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

Image by GeeksforGeeks
Image by GeeksforGeeks

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:

  • If a smaller numeral comes after a larger numeral, add the smaller number to the larger number;
  • If a smaller numeral comes before a larger numeral, subtract the smaller number from the larger number;
  • Do not use the same symbol more than three times in a row.
  • Modern usage employs seven symbols, each with a fixed integer value:
  • Keeping in mind these rules, here are few examples:

  • 399 in Roman Numerals is CCCXCIX
  • 151 in Roman Numerals is CLI
  • 185 in Roman Numerals is CLXXXV
  • 3070 in Roman Numerals is MMMLXX
  • 570 in Roman Numerals is DLXX
  • 7 in Roman Numerals is VII
  • 290 in Roman Numerals is CCXC
  • 1880 in Roman Numerals is MDCCCLXXX
  • 47 in Roman Numerals is XLVII
  • Let’s go through our solution now step by step: 🎢.

  • Create a data structure that will hold the representations of the known numbers from the rules, in our case it is a simple JavaScript object
  • Add few more known numbers that will help us in the later calculations, i.e. 4, 9, 20, 30, 40, 90, 400 and 900
  • Create an empty string that will hold the result
  • Then use Object.keys() method to get all Roman numerals from our structure
  • Iterate through them via a for- loop
  • For each Roman letter, get its Arabic counterpart and check if it’s less or equal to the number we are converting
  • If it is, first subtract it from the number we are converting and then store the current Roman representation in the res string, by concatenating it with what’s already there.
  • After both loops finish, return what’s the end result in our string variable
  • 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.

    Possible improvement using more letters. Source: by
    Possible improvement using more letters. Source: by

    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:

    https://www.solidjs.com/

    You earned a free joke. Rate this article to get it.
    ← Go home