Свифт функция для интерпретации римскими цифрами (портирована из JavaScript)


Я написал некоторые SWIFT впервые, будучи достаточно компетентным в JavaScript и имея некоторый опыт в Ruby и Python. Для моего образования, я написал функцию, которая разбирает строку римские цифры и возвращает его целочисленное представление, первое в JavaScript (ES2015+):

const dict = [
  ['CM', 900], ['M', 1000], ['CD', 400], ['D', 500],
  ['XC', 90], ['C', 100], ['XL', 40], ['L', 50],
  ['IX', 9], ['X', 10], ['IV', 4], ['V', 5],
  ['I', 1],
]

function romanToInt (original) {
  let temp = original
  let int = 0

  while (temp.length > 0) {
    let found = false

    for (const [glyph, quantity] of dict) {
      if (temp.startsWith(glyph)) {
        int += quantity
        temp = temp.slice(glyph.length)
        found = true
        break
      }
    }

    if (!found) {
      // e.g. Error parsing roman numeral "MDCCLXXVI," at ","
      throw new Error(`Error parsing roman numeral "${original}" at "${temp}"`)
    }
  }

  return int
}

try {
  romanToInt('MMXIV') // => 2014
} catch (err) {
  console.error(err)
}

а затем ее портировали на Свифт 4:

let dict: [( glyph: String, quantity: Int )] = [
    ("CM", 900), ("M", 1000), ("CD", 400), ("D", 500),
    ("XC", 90), ("C", 100), ("XL", 40), ("L", 50),
    ("IX", 9), ("X", 10), ("IV", 4), ("V", 5),
    ("I", 1)
]

enum RomanNumericError: Error {
    case badInput(original: String, temp: String)
}

func romanToInt(original: String) throws -> Int {
    var temp = original
    var int = 0

    while temp.count > 0 {
        var found = false

        for (glyph, quantity) in dict {
            if temp.starts(with: glyph) {
                int += quantity
                temp.removeFirst(glyph.count)
                found = true
                break
            }
        }

        guard found == true else {
            throw RomanNumericError.badInput(original: original, temp: temp)
        }
    }

    return int
}

do {
    try romanToInt(original: "MMXIV") // => 2014
} catch RomanNumericError.badInput(let original, let temp) {
    print("Error parsing roman numeral '\(original)' at '\(temp)'")
}

Я задаюсь вопросом о том, как Свифт-Г мой код с точки зрения шаблонов проектирования, особенно в плане обработки ошибок. В JavaScript, бросать и ловить ошибки-это очень распространенная управления потоком, и я интересно, если я подхожу к ней с правого угла в Swift.



Комментарии
1 ответ

Давайте сделаем это изнутри. temp.starts(with: glyph) это правильно, но
существует также специальный метод temp.hasPrefix(glyph) для строк.

Цикл, чтобы найти первую запись словаря с соответствующим префиксом
может быть сокращен до

guard let (glyph, quantity) = dict.first(where: { temp.hasPrefix($0.glyph) }) else {
throw RomanNumericError.badInput(original: original, temp: temp)
}

(также делает var found устар.)

Мутирует временную строку можно избежать, работая с SubString (разновидностью которого является представление исходной строки) и
только обновление текущей позиции поиска:

var pos = original.startIndex
while pos != original.endIndex {
let subString = original[pos...]
// ...
pos = original.index(pos, offsetBy: glyph.count)
}

Название: это очень личное мнение, вот мое мнение:


  • Объявить функцию как func romanToInt(_ roman: String),
    так что это называется без (внешнего) аргумента имя:
    romanToInt("MMXIV").

  • Переименовать var int для var value.

  • dict тоже не описательное имя (и это даже не словарь), что-то вроде glyphsAndValues может быть лучшим выбором.

Обобщая предложения до сих пор мы

func romanToInt(_ roman: String) throws -> Int {
var value = 0
var pos = roman.startIndex
while pos != roman.endIndex {
let subString = roman[pos...]
guard let (glyph, quantity) = glyphsAndValues.first(where: { subString.hasPrefix($0.glyph) }) else {
throw RomanNumericError.badInput(roman: roman, at: subString)
}
value += quantity
pos = roman.index(pos, offsetBy: glyph.count)
}
return value
}


Теперь обработку ошибки. Да, ошибки-это хорошо и Свифти
способом сообщают об отказе вызывающего абонента. (В качестве альтернативы предлагается вернуть
необязательное значение, которое nil в случае ошибки, но это не
позвольте представить дополнительную информацию об ошибке.)

Однако создание сообщение об ошибке должно быть сделано в
ошибки класса, приняв LocalizedError протокол:

enum RomanNumericError: Error {
case badInput(roman: String, at: Substring)
}

extension RomanNumericError: LocalizedError {
public var errorDescription: String? {
switch self {
case .badInput(let roman, let at):
return "Error parsing roman numeral '\(roman)' at '\(at)'"
}
}
}

Большим преимуществом является то, что абоненту не нужно знать, что
ошибка эта функция создает, и универсальный Error:

do {
try print(romanToInt("MMXIV"))
try print(romanToInt("MMYXIV"))
} catch {
print(error.localizedDescription)
}

// Output:
// 2014
// Error parsing roman numeral 'MMYXIV' at 'YXIV'

4
ответ дан 26 января 2018 в 09:01 Источник Поделиться