Reputation: 3463
I am looking to take an Integer
in Swift and convert it to a Roman Numeral String
. Any ideas?
Upvotes: 3
Views: 4289
Reputation: 3463
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 "Invalid input (greater than 3999)"
}
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.
Upvotes: 8
Reputation: 1
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
}
}
Upvotes: -2
Reputation: 95
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
}
}
Upvotes: 1
Reputation:
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 { }
Upvotes: 2
Reputation: 207
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
}
}
}
Upvotes: 0
Reputation: 263
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
}
}
Upvotes: 3