Brian Sachetta
Brian Sachetta

Reputation: 3463

Convert Integer to Roman Numeral String in Swift

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

Upvotes: 3

Views: 4289

Answers (6)

Brian Sachetta
Brian Sachetta

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

Mekhak Ghapantsyan
Mekhak Ghapantsyan

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

RichStwrt
RichStwrt

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

user652038
user652038

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

P. A. Monsaille
P. A. Monsaille

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

Guillaume Ramey
Guillaume Ramey

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

Related Questions