在Swift中将整数转换为罗马数字字符串

3
我想将Swift中的一个整数Integer转换为罗马数字String,有什么建议吗?

这是一个很好的练习,可以用测试驱动开发来完成。先让它在1上运行,然后是2,... - Jon Reid
6个回答

7

可以编写一个与下面所见类似的Int扩展。

请注意:此代码将对小于一的数字返回“”。虽然在罗马数字中这可能是可以接受的(零不存在),但您可能希望在自己的实现中以不同的方式处理此问题。

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
    }
}

感谢Kenneth Bruno提供的一些有关改善代码方面的建议。


1
你可以通过将第6行和第7行改为for i in mappingList where (integerValue >= i.0) {,并将所有的mappingList[i]替换为i来简化该代码。我还会将其设置为计算属性,这是一个很好的选择。对我来说,这似乎是一个不错的算法! - user887210
感谢你的建议,@KennethBruno。我已经更新了代码,使用了改进后的for循环声明。另外,我对Swift还不太熟悉,所以一有机会我会进一步研究一下computed property用于mappingList的情况。 - Brian Sachetta
好的,知道了,谢谢。我一定会考虑的。不过我认为 while 循环仍然是必要的,因为你可以有多个相同字母的情况。例如,3 = "III"。 - Brian Sachetta
1
这段代码实际上会抛出一个错误,因为在第6行你返回了self,尽管返回类型应该是String,而self是一个Int。可以通过将第6行的self替换为self.description来修复。谢谢你的回答! - undefined
好的决定!会更新以反映这一点。 - undefined
显示剩余3条评论

3
这是我编写的一个整数转罗马数字的程序(无嵌套循环):

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
    }
}

非常出色的工作。 - ZAY

1
罗马数字可以被看作是一种密码。你可以为组合情况编写规则,但它们并不那么一致,所以最好将它们处理为实际情况。
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 { }

1
Brian Sachetta的版本补充。如果你想超过4999,你可以使用增强版罗马数字集合。这个集合中最大的数字是8,999,999,999,999,即OZZZQZUQBUGBTGRTHREHMECMXCIX。集合使用拉丁字母表中的所有字母。
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
    }
}

0

再来一个以确保没有问题:

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
            }
    }
}

-2
    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
    }
}

这不是一个罗马数字。FYI - 罗马数字是 I、II、III、IV、V、VI 等等。 - HangarRash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接