Learn how to build iOS apps
Get started with iOS 14 and Swift 5
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
Roman numerals, such as MMXIX, are exciting! In this article, we’re going to write some code that converts any integer number to a roman numeral. It’s a fun, short exercise that touches on many aspects of the Swift programming language. It’s perfect if you have a spare minute or two to play with code!
Ready? Let’s go.
A big thanks to Les from the United Kingdom, for the inspiration to write this article! Keep having fun coding :-)
Before we start, let’s do a quick refresher on roman numerals. Roman numerals are a numeric system that originated in ancient Rome. With it, a number like 2019 is written as MMXIX, in letters of the Latin alphabet.
Roman numerals use 7 symbols, each with a fixed integer number:
Symbol | I | V | X | L | C | D | M |
---|---|---|---|---|---|---|---|
Value | 1 | 5 | 10 | 50 | 100 | 500 | 1000 |
Roman numerals were used until the late Middle Ages (1500 CE), but in our modern world they still have their uses! You see roman numerals on clocks, paper currency, buildings, monuments, and even movie credits.
The roman numerals system is basically a decimal or “base 10” number system, written from left to right. Instead of each power of ten having its own decimal place, roman numerals “add up” symbols to a given number. It’s easiest to think of them as “tally marks.”
For example, the number 2019 is written as:
That last symbol, IX, is special. Instead of writing VIIII, for “5 + 4 = 9”, you write IX, for “10 – 1 = 9”. This is called “subtractive notation”. It’s quite useful, because it’ll save you from writing lots of symbols! IX is simply shorter than VIIII.
The same trick applies to other symbols too. For example, 40 is XL instead of XXXX, 90 is XC instead of LXXXX, 400 is CD, and 900 is CM. It doesn’t work for non-adjacent symbols, i.e. 999 is not IM but CMXCIX.
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
We’re going to write a Swift function that can convert arbitrary integer numbers to roman numerals. Let’s get started!
First, let’s think about the input and output for this function. The function needs an Int
value as input, and outputs a value of type String
. So, our function declaration will be this:
func roman(number: Int) -> String
{
}
The roman(number:)
function takes a value of type Int
for the number
parameter and outputs a value of type String
. Perfect!
Next, inside the function, we’re going to define the roman numeral system, like this:
let decimals = [1000, 500, 100, 50, 10, 5, 1]
let numerals = ["M", "D", "C", "L", "X", "V", "I"]
The above code defines exactly the same as the table in the previous section. Every decimal value has a roman numeral, such as C = 100
. Both arrays have exactly the same amount of items, so we can use identical index numbers to look up numerals by decimals.
Two things stand out here:
Next, we’re coding this inside the function:
var result = ""
var number = number
The above code first defines a variable result
of type String
. We’re going to use this variable to build up the resulting roman numeral, i.e. the output string.
The local variable number
is declared with the function parameter number
(which is a bit ugly). Parameters are constants, so they can’t be changed. By “redeclaring” number
as a variable, we’re now allowed to change number
. We couldn’t have used the inout
keyword here, because that would have produced the unintended side-effect of changing the function’s argument, as you’ll see later on.
Why don’t we use a dictionary for decimals
and numerals
? Well, dictionary items don’t have a sort order! The algorithm we’re about to write relies on a larger-to-smaller sort order, which won’t work with dictionaries. An alternative would be using tuples, or sorting the dictionary and decomposing its keys and values – but what we have now is much simpler.
Alright, we’re getting to the core of the algorithm. Before we start, it’s worthwhile to consider how we would convert a number to roman numerals with pen and paper. This is always a great idea if you need to design an algorithm.
Here’s how we would convert 1776:
We could say that we’re subtracting numbers from 1776 until we reach zero. Each number we subtract results in a different symbol added to the final result. Would it be possible to put that workflow into an algorithm?
First, let’s start with a while
loop. Like this:
while number > 0
{
}
This loop will keep iterating for as long as number
is greater than 0
. Differently said, it’ll stop if number
is zero. We don’t know how many iterations we need, so we’re using while
instead of a for loop.
Next, we want to loop over every item in the decimals
array. We’re going to attempt to subtract this decimal number from number
. That’s why we sorted the decimals
array from largest-to-smallest, so we can see if a decimal would “fit into” the input number.
So, we write this inside the while
loop:
for (index, decimal) in decimals.enumerated()
{
if number - decimal >= 0 {
}
}
The for loop is used to iterate over the decimals
array. We’re using the enumerated()
function so we can access both the array index and the array value, with the index
and decimal
constants in the tuple.
Inside the for loop, we’re checking if number
minus decimal
is greater or equal to zero, with an if block. This is exactly what we did in our pen-and-paper workflow, earlier.
1000 = M
for number = 1776
. Is 1776 minus 1000 greater than zero? Yes it is! We now know that 1776 can accommodate one M for its thousand.500 = D
for number = 99
. Is 99 minus 500 greater than zero? No it’s not! We now know that the number 99 doesn’t include a D.Let’s finish the code by writing the following inside the if
block:
number -= decimal
result += numerals[index]
break
Here’s what happens on those 3 lines:
decimal
is subtracted from number
. When we’ve found that a decimal “fits in” the input number, we’ve found a roman numeral, so that decimal can be subtracted from the input number.numerals[index]
is appended to result
, so the found roman numeral in the iteration is written down by using index
. Remember that index
corresponds to the index number of both the decimal and the roman numeral!break
exits this iteration of the for
loop, because we’ve found a roman literal, and thus the while
loop continues with its next iterationFinally, we return the resulting value in the function with:
return result
Here’s the complete code once more:
func roman(number: Int) -> String
{
let decimals = [1000, 500, 100, 50, 10, 5, 1]
let numerals = ["M", "D", "C", "L", "X", "V", "I"]
var result = ""
var number = number
while number > 0
{
for (index, decimal) in decimals.enumerated()
{
if number - decimal >= 0 {
number -= decimal
result += numerals[index]
break
}
}
}
return result
}
Let’s go over the core principles of the algorithm.
while
loop ensures that we’re iterating for as long as number
is greater than 0
. If number
equals 0
, the algorithm stops. The code that subtracts something from number
, is inside the for
loop: number -= decimal
. When we’ve found a roman numeral, we’re subtracting its value, to move towards zero.for
loop, and its inner if
block, is what checks every decimal against the current number
value. It’s as if it’s trying to “fit” a roman numeral inside the number. Because, if it fits, we’ve got a match!number
and append the numeral to result
. The loop also needs to be broken with break
, because the loop needs to start over from the beginning.You can see the algorithm do its work more clearly, by adding the following line inside the if
block:
print("Found \(numerals[index]) for \(decimal)")
You can run the function with the following code:
print(roman(number: 2019))
print(roman(number: 1776))
print(roman(number: 1999))
For the year 2019, the output is:
Found M for 1000
Found M for 1000
Found X for 10
Found V for 5
Found I for 1
Found I for 1
Found I for 1
Found I for 1
But… what’s going on there!? Wasn’t 2019 supposed to be MMXIX?
We haven’t yet incorporated subtractive notation in our algorithm. We discussed before that some symbols, such as for 9, are written as IX or “one before 10”, instead of VIIII.
The algorithm we’ve coded so far works by attempting to subtract decimals from the input value, substituting them for roman numerals. We could adjust the algorithm to incorporate numerals like IX with some complicated code, but as it turns out, supporting subtractive notation is incredibly simple.
Replace the declaration at the top of the function with:
let decimals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
let numerals = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
Instead of only using the basic values M, D, C, L, X, V, and I, we’ve simply added the other symbols, like CM, to the array as well. For our algorithm, it doesn’t matter much if you subtract 900, or 500 and 4 times 100! As long as the decimals and numerals are in descending order, the subtraction mechanism works perfectly fine.
And that’s everything there’s to it! Here, try out the code for yourself with this Swift sandbox:
func roman(number: Int) -> String
{
let decimals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
let numerals = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
var result = ""
var number = number
while number > 0
{
for (index, decimal) in decimals.enumerated()
{
if number - decimal >= 0 {
number -= decimal
result += numerals[index]
break
}
}
}
return result
}
print(roman(number: 2019))
print(roman(number: 1776))
print(roman(number: 1999))
Awesome! We’ve converted any arbitrary decimal value to roman numerals. It’s a fun, short programming exercise that touches on many interesting parts of Swift syntax.
But… what about converting numerals back to integer values? We’ll leave that for another day! If you’re up for a challenge, however, try to find the longest roman numeral between 1 and 3000.
Want to learn more? Check out these Swift exercises:
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
Hi, I'm Reinder.
I help developers play with code.
Build great iOS apps
Learn how in my free 7-day course
No spam, ever. Unsubscribe anytime. Privacy Policy