Play with Code: Converting Roman Numerals with Swift
Roman numerals, such as MMXIX, are exciting! In this tutorial, 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.
- What Are Roman Numerals?
- Setting Up The “roman(number:)” Function
- Converting Numbers To Roman Numerals
- Taking Numerals Like “IX” Into Account
- Further Reading
A big thanks to Les from the United Kingdom, for the inspiration to write this tutorial! Keep having fun coding :-)
What Are Roman Numerals?
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:
- MM for 2 times a thousand
- X for once a ten
- IX for “one before 10”, i.e. 9
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.
Setting Up The “roman(number:)” Function
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:
- We’ve sorted the arrays from largest-to-smallest. This is crucial for our implementation, as you’ll see later on.
- We haven’t yet added the IX, CM, etc. “subtractive notation” symbols, which we’ll do later.
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.
Converting Numbers To Roman Numerals
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:
- See that we need one M, for a thousand – which leaves 776
- See that we need DCC, for “500 + 100 + 100 = 700”, which leaves 76
- See that we need LXX, for “50 + 10 + 10 = 70”, which leaves 6
- Finally, we need VI for “5 + 1 = 6”, resulting in MDCCLXXVI
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.
- Take for example the
1000 = M
fornumber = 1776
. Is 1776 minus 1000 greater than zero? Yes it is! We now know that 1776 can accommodate one M for its thousand. - Another example:
500 = D
fornumber = 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 fromnumber
. 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 toresult
, so the found roman numeral in the iteration is written down by usingindex
. Remember thatindex
corresponds to the index number of both the decimal and the roman numeral! -
break
exits this iteration of thefor
loop, because we’ve found a roman literal, and thus thewhile
loop continues with its next iteration
Finally, 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.
- The
while
loop ensures that we’re iterating for as long asnumber
is greater than0
. Ifnumber
equals0
, the algorithm stops. The code that subtracts something fromnumber
, is inside thefor
loop:number -= decimal
. When we’ve found a roman numeral, we’re subtracting its value, to move towards zero. - The
for
loop, and its innerif
block, is what checks every decimal against the currentnumber
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! - When the algorithm finds a roman numeral, for example M for a 1000 in 1776, it’ll subtract that decimal from
number
and append the numeral toresult
. The loop also needs to be broken withbreak
, 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?
Taking Numerals Like “IX” Into Account
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:
{
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))
Further Reading
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:
Most Popular
- How To Learn iOS App Development
- How To Make An App with Swift and Xcode (In 9 Steps)
- Pass Data Between View Controllers in Swift
- How To Solve SIGABRT Error in Xcode
- Learn Swift Programming The Simple Way
- Create an iOS Game with Swift and Xcode