6

I have this enumeration representing a color, and I have added several methods to conveniently obtain new instances based on arithmetic operations on the original's raw value:

enum Color : Int
{
    case Red = 0
    case Green
    case Blue

    case Cyan
    case Magenta
    case Yellow


    static func random() -> Color
    {
        return Color(rawValue: Int(arc4random_uniform(6)))!
    }

    func shifted(by offset:Int) -> Color
    {
        return Color(rawValue: (self.rawValue + offset) % 6)!
        // Cyclic: wraps around
    }
}

(This harks back to the old enums being just int constants)

The problem is, I have several other int-based enums where I would like to introduce similar functionality, but without duplicating code.

I think I should define a protocol extension on RawRepresentable where RawValue == Int:

extension RawRepresentable where RawValue == Int
{

...but that's where my understanding of the syntax ends.

Ideally, I would like to require a static method returning the number of cases, and a provide default implementation of both random() and shifted(_:) above that takes that into account (instead of the hard-coded 6 here).

CONCLUSION: I have accepted the answer by Zoff Dino. Even though the answer given by Rob Napier is exactly what I asked for, it turns out what I was asking for was not the most elegant design after all, and the other answer suggests a better approach. Still, I have upvoted both answers; thanks everyone.

Community
  • 1
  • 1
Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189

2 Answers2

5

You should extend your custom protocol instead of RawRepresentable. Try this:

protocol MyProtocol {
    static var maxRawValue : Int { get }

    static func random() ->  Self
    func shifted(by offset: Int) -> Self
}

enum Color : Int, MyProtocol
{
    case Red = 0
    case Green
    case Blue

    case Cyan
    case Magenta
    case Yellow

    // The maximum value of your Int enum
    static var maxRawValue: Int {
        return Yellow.rawValue
    }
}

extension MyProtocol where Self: RawRepresentable, Self.RawValue == Int {
    static func random() -> Self {
        let random = Int(arc4random_uniform(UInt32(Self.maxRawValue + 1)))
        return Self(rawValue: random)!
    }

    func shifted(by offset: Int) -> Self {
        return Self(rawValue: (self.rawValue + offset) % (Self.maxRawValue + 1))!
    }
}

let x = Color.random()
let y = x.shifted(by: 1)
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Hmm... it seems that your solution forces me to define and adopt a protocol on each of my `enum`s, but has the benefit of not contaminating all int-based enums, right? I guess this is the wiser solution, since it does not rely on some extension placed somewhere and makes my enums more self-contained and explicit. Otherwise, Robs's answer seems simpler... – Nicolas Miari Nov 07 '15 at 05:07
  • 1
    You can combine my answer with Nate Cook's case-counting code to make it simpler. Yes, I prefer only the enums I choose to have these methods. It would be weird to see `random` to pop up on `NSRegularExpressionOptions`. – Code Different Nov 07 '15 at 05:15
4

You're almost there. You just need Nate Cook's case-counting code from https://stackoverflow.com/a/27094913/97337.

extension RawRepresentable where RawValue == Int {
    // See http://natecook.com/blog/2014/10/loopy-random-enum-ideas/
    static var caseCount: Int {
        var max: Int = 0
        while let _ = self.init(rawValue: ++max) {}
        return max
    }

    static func random() -> Self {
        return Self(rawValue: Int(arc4random_uniform(UInt32(caseCount))))!
    }

    func shifted(by offset:Int) -> Self {
        return Self(rawValue: (self.rawValue + offset) % self.dynamicType.caseCount)!
        // Cyclic: wraps around
    }
}
Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I like how you count the number of cases by attempting `init?(rawValue:)` until it fails (because the passed integer is too big). – Nicolas Miari Nov 07 '15 at 05:10