I am looking to take an Integer
in Swift and convert it to a Roman Numeral String
. Any ideas?

- 229,809
- 59
- 489
- 571

- 3,319
- 2
- 34
- 45
-
This is a good exercise to do with test-driven development. Get it working for 1, then 2, … – Jon Reid Mar 14 '23 at 03:46
6 Answers
One could write an extension on Int
, similar to the one seen below.
Please note: this code will return "" for numbers less than one. While this is probably okay in terms of Roman Numeral numbers (zero does not exist), you may want to handle this differently in your own implementation.
extension Int {
var romanNumeral: String {
var integerValue = self
// Roman numerals cannot be represented in integers greater than 3999
if self >= 4000 {
return self
}
var numeralString = ""
let mappingList: [(Int, 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 i in mappingList {
while (integerValue >= i.0) {
integerValue -= i.0
numeralString += i.1
}
}
return numeralString
}
}
Thanks to Kenneth Bruno for some suggestions on improving the code as well.

- 2,184
- 25
- 18

- 3,319
- 2
- 34
- 45
-
1You can simplify that code by changing line 6 and 7 to `for i in mappingList where (integerValue >= i.0) {` and all `mappingList[i]` to `i`. I'd also make it a computed property, it's a good fit for that. Seems like a good algorithm to me! – Mar 17 '16 at 20:12
-
Thanks for the suggestions, @KennethBruno. I updated the code to use the revamped for loop declaration. Also, I'm new to swift, so when I get the chance I will look further into the computed property for the mappingList. – Brian Sachetta Mar 17 '16 at 20:56
-
To make it a computed property change line 2 to `var romanNumeral: String {`. Then you would call it as `number.romanNumeral`, just like a regular property. Oh and remove line 7 and 10, you don't need them any more. – Mar 17 '16 at 20:59
-
Ok, good to know, thanks. I will certainly consider that. I think the while loop is still necessary, however, since you can have multiple instances of the same letter. For example, 3 = "III". – Brian Sachetta Mar 18 '16 at 01:12
-
-
`for i in mappingList {` would suffice. That first where condition is redundant considering that the `while` method already takes care of it inside the closure. – Leo Dabus Mar 08 '19 at 18:32
Here's my version of an int to roman converter (without nested loop) :
extension Int {
func toRoman() -> String {
let conversionTable: [(intNumber: Int, romanNumber: 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")]
var roman = ""
var remainder = 0
for entry in conversionTable {
let quotient = (self - remainder) / entry.intNumber
remainder += quotient * entry.intNumber
roman += String(repeating: entry.romanNumber, count: quotient)
}
return roman
}
}

- 205
- 2
- 5
An addition to Brian Sachetta's version. If you want to go beyond 4999, you can use set of Enhanced Roman Numerals. Largest number in this set is 8,999,999,999,999, which is OZZZQZUQBUGBTGRTHREHMECMXCIX. Set uses all letters in Latin Alphabet.
extension Int {
var romanNumeral: String {
var integerValue = self
var numeralString = ""
let mappingList: [(Int, String)] = [(5000000000000, "O"), (4000000000000, "ZO"), (1000000000000, "Z"),
(900000000000, "QZ"), (500000000000, "Y"), (400000000000, "QY"), (100000000000, "Q"),
(90000000000, "UQ"), (50000000000, "W"), (40000000000, "UW"), (10000000000, "U"),
(9000000000, "BU"), (5000000000, "A"), (4000000000, "BA"), (1000000000, "B"),
(900000000, "GB"), (500000000, "J"), (400000000, "JG"), (100000000, "G"),
(90000000, "TG"), (50000000, "S"), (40000000, "TS"), (10000000, "T"),
(9000000, "RT"), (5000000, "P"), (4000000, "RP"), (1000000, "R"),
(900000, "HR"), (500000, "K"), (400000, "HK"), (100000, "H"),
(90000, "EH"), (50000, "F"), (40000, "EF"), (10000, "E"),
(9000, "ME"), (5000, "N"), (4000, "MN"), (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 i in mappingList {
while (integerValue >= i.0) {
integerValue -= i.0
numeralString += i.1
}
}
return numeralString
}
}

- 7,314
- 5
- 5
- 32

- 65
- 6
Roman Numerals can be thought of as a cipher. You can program the rules for the compound cases, but they're not that consistent, so it's better to handle them as actual cases.
String([RomanNumeral](3456)) // MMMCDLVI
import Algorithms
/// A cipher between numbers and strings.
/// - Precondition: `allCases` is sorted.
public protocol NumericCipher: RawRepresentable & CaseIterable
where RawValue: BinaryInteger, AllCases: BidirectionalCollection { }
public extension Array where Element: NumericCipher {
init(_ number: Element.RawValue) {
self = .init(
sequence(
state: (remainder: number, index: Element.allCases.indices.last!)
) { state in
guard let (index, element) = Element.allCases.indexed()
.prefix(through: state.index)
.last(where: { $0.element.rawValue <= state.remainder })
else { return nil }
state.remainder -= element.rawValue
state.index = index
return element
}
)
}
}
public extension String {
init(_ cipher: some Sequence<some NumericCipher>) {
self = cipher.map { "\($0)" }.joined()
}
}
public enum RomanNumeral: Int {
case i = 1
case iv = 4
case v = 5
case x = 10
case xl = 40
case l = 50
case xc = 90
case c = 100
case cd = 400
case d = 500
case cm = 900
case m = 1000
}
extension RomanNumeral: CustomStringConvertible {
public var description: String {
switch self {
case .i: return "I"
case .iv: return "\(Self.i)\(Self.v)"
case .v: return "V"
case .x: return "X"
case .xl: return "\(Self.x)\(Self.l)"
case .l: return "L"
case .xc: return "\(Self.x)\(Self.c)"
case .c: return "C"
case .cd: return "\(Self.c)\(Self.d)"
case .d: return "D"
case .cm: return "\(Self.c)\(Self.m)"
case .m: return "M"
}
}
}
extension RomanNumeral: NumericCipher { }
One more for good measure:
fileprivate let romanNumerals: [String] = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
fileprivate let arabicNumerals: [Int] = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
extension Int {
var romanRepresentation: String {
guard self > 0 && self < 4000 else {
return "Invalid Number"
}
var control: Int = self
return zip(arabicNumerals, romanNumerals)
.reduce(into: "") { partialResult, ar in
partialResult += String(repeating: ar.1, count: control/ar.0)
control = control % ar.0
}
}
}

- 152
- 1
- 1
- 10
extension Int {
func convertToOrdinal() -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .ordinal
guard let ordinalString = numberFormatter.string(from: NSNumber(value: self)) else {
return "\(self)"
}
return ordinalString
}
}
-
This does not generate a Roman numeral. FYI - Roman numbers are I, II, III, IV, V, VI, etc. – HangarRash May 17 '23 at 14:52