arun siva
arun siva

Reputation: 869

How to simplify Swift Enum custom init

I've crated an Enum with String type. It has two init methods. One is default init method with rawValue and another one is custom init method with intValue. I've written it like this. Is there any simple way to not to use two switch cases?

enum Roman: String {
    case I,V,X,L,C,D,M

    var intValue: Int {
        switch self {
        case .I:
            return 1
        //...
        }
    }
    init?(intValue: Int) {
        switch intValue {
        case 1:
            self = .I
        //...
        default:
            return nil
        }
    }
}

    //Roman to Int
    let number = "XXI".reversed()
                    .map { Roman(rawValue: String($0))?.intValue ?? 0 }
                    .reduce((total: 0, max: 0)) { result, value in
                        let newTotal = result.total + (value < result.max ? -value : value)
                        return (newTotal, max(result.max, value))
                    }.total

Upvotes: 2

Views: 5519

Answers (3)

Rob Napier
Rob Napier

Reputation: 299565

There's nothing wrong with Dávid Pásztor's answer, but I do really like Βασίλης Δ.'s thinking raw values. That just seems a very natural approach. So I'd like to bring those's together.

First, starting with Βασίλης Δ.'s code, adding an intValue alias just because I think it reads a bit better.

enum Roman: Int {
    case I = 1
    case V = 5
    case X = 10
    case L = 50
    case C = 100
    case D = 500
    case M = 1000

    var intValue: Int { return rawValue }
}

Then provide a lookup for Strings using the new CaseIterable:

extension Roman: CaseIterable {
    enum Error: Swift.Error {
        case invalid
    }

    init<S: StringProtocol>(_ string: S) throws {
        guard let roman = Roman.allCases.first(where: { "\($0)" == string }) else {
            throw Error.invalid
        }
        self = roman
    }

    init(_ character: Character) throws { try self.init(String(character)) }
}

With that, I think the number algorithm gets a little bit nicer at the top:

let number = try "XXI".reversed()
    .map { try Roman($0).intValue }
    .reduce((total: 0, max: 0)) { result, value in
        let newTotal = result.total + (value < result.max ? -value : value)
        return (newTotal, max(result.max, value))
    }.total

I'm not a big fan of this algorithm, because it behaves erratically for invalid input, but at least this version rejects invalid characters rather than converting them to 0.

Upvotes: 3

David Pasztor
David Pasztor

Reputation: 54755

You can get rid of the switch statements by defining two dictionaries for a bidirectional mapping between the Int values and the enum cases.

enum Roman: String {
    case I, V, X, L, C, D, M

    private static let intValues:[Roman:Int] = [.I:1,.V:5,.X:10,.L:50,.C:100,.D:500,.M:1000]
    private static let mappingDict:[Int:Roman] = Dictionary(uniqueKeysWithValues: Roman.intValues.map({ ($1, $0) }))

    var intValue:Int {
        return Roman.intValues[self]!
    }

    init?(intValue:Int){
        guard let roman = Roman.mappingDict[intValue] else { return nil }
        self = roman
    }
}

Upvotes: 7

Vasilis D.
Vasilis D.

Reputation: 1456

If I understood correctly what you want.. Why don't assign directly the values to the enumerator cases? Ex.

    enum Roman: Int {
        case I = 1
        case V = 5
        case X = 10
        case L = 50
        case C = 100
        case D = 500
        case M = 1000
    }

And on your main class

print(Roman.I.rawValue)
print(Roman(rawValue: 1))

Upvotes: 0

Related Questions