简体   繁体   中英

How to choose a random enumeration value

I am trying to randomly choose an enum value:

enum GeometryClassification {

    case Circle
    case Square
    case Triangle
    case GeometryClassificationMax

}

and the random selection:

let shapeGeometry = ( arc4random() % GeometryClassification.GeometryClassificationMax ) as GeometryClassification

but it fails.

I get errors like:

'GeometryClassification' is not convertible to 'UInt32'

How do I solve this?

In Swift there is actually a protocol for enums called CaseIterable that, if you add it to your enum, you can just reference all of the cases as a collection with .allCases as so:

enum GeometryClassification: CaseIterable {

    case Circle
    case Square
    case Triangle

}

and then you can .allCases and then .randomElement() to get a random one

let randomGeometry = GeometryClassification.allCases.randomElement()!

The force unwrapping is required because there is a possibility of an enum having no cases and thus randomElement() would return nil .

Swift has gained new features since this answer was written that provide a much better solution — see " How to choose a random enumeration value " instead.


I'm not crazy about your last case there -- it seems like you're including .GeometryClassificationMax solely to enable random selection. You'll need to account for that extra case everywhere you use a switch statement, and it has no semantic value. Instead, a static method on the enum could determine the maximum value and return a random case, and would be much more understandable and maintainable.

enum GeometryClassification: UInt32 {
    case Circle
    case Square
    case Triangle

    private static let _count: GeometryClassification.RawValue = {
        // find the maximum enum value
        var maxValue: UInt32 = 0
        while let _ = GeometryClassification(rawValue: maxValue) { 
            maxValue += 1
        }
        return maxValue
    }()

    static func randomGeometry() -> GeometryClassification {
        // pick and return a new value
        let rand = arc4random_uniform(_count)
        return GeometryClassification(rawValue: rand)!
    }
}

And you can now exhaust the enum in a switch statement:

switch GeometryClassification.randomGeometry() {
case .Circle:
    println("Circle")
case .Square:
    println("Square")
case .Triangle:
    println("Triangle")
}

Since you're inside the enum class, having the random() method reference the highest value explicitly would eliminate having to count them every time:

enum GeometryClassification: UInt32 {
    case Circle
    case Square
    case Triangle

    static func random() -> GeometryClassification {
        // Update as new enumerations are added
        let maxValue = Triangle.rawValue

        let rand = arc4random_uniform(maxValue+1)
        return GeometryClassification(rawValue: rand)!
    }
}

For Swift 5 there is " RandomNumberGenerator ":

enum Weekday: CaseIterable {
    case sunday, monday, tuesday, wednesday, thursday, friday, saturday

    static func random<G: RandomNumberGenerator>(using generator: inout G) -> Weekday {
        return Weekday.allCases.randomElement(using: &generator)!
    }

    static func random() -> Weekday {
        var g = SystemRandomNumberGenerator()
        return Weekday.random(using: &g)
    }
}

You need to assign a raw type to your enum. If you use an integer type, then the enumeration case values will be auto-generated starting at 0:

enum GeometryClassification: UInt32 {  
    case Circle
    case Square
    case Triangle
    case GeometryClassificationMax
}

Per Enumerations :

"Unlike C and Objective-C, Swift enumeration members are not assigned a default integer value when they are created."

Specifying the integer type lets it know to generate the values in the usual way.

Then you can generate the random value like this:

let randomEnum: GeometryClassification = GeometryClassification.fromRaw(arc4random_uniform(GeometryClassification.GeometryClassificationMax.toRaw()))!

This is a horribly ugly call, and all those fromRaw and toRaw calls are fairly inelegant, so I would really recommend generating a random UInt32 that is in the range you want first, then creating a GeometryClassification from that value:

GeometryClassification.fromRaw(someRandomUInt32)

You can put all the values into array and generate random,

extension GeometryClassification {

    static func random() -> GeometryClassification {
        let all: [GeometryClassification] = [.Circle,
                                             .Square,
                                             .Triangle,
                                             .GeometryClassificationMax]
        let randomIndex = Int(arc4random()) % all.count
        return all[randomIndex]
    }
}

Here's my Swift 1.2 take:

enum GeometryClassification : Int {
    case Circle = 0
    case Square = 1
    case Triangle = 2

    static func random() -> GeometryClassification {
        let min = MutationType.Circle.rawValue
        let max = MutationType.Triangle.rawValue
        let rand = Int.random(min: min, max: max) // Uses ExSwift!
        return self(rawValue: rand)!
    }
}

I wrote a global extension using Andy's answer. Enjoy :)

extension CaseIterable {
    static func random<G: RandomNumberGenerator>(using generator: inout G) -> Self.AllCases.Element {
        return Self.allCases.randomElement(using: &generator)!
    }

    static func random() -> Self.AllCases.Element {
        var g = SystemRandomNumberGenerator()
        return Self.random(using: &g)
    }
}

Just extend your enumeration to conform CaseIterable protocol and use like:

let state = YourEnum.random()

The easiest thing to do is to create a global extension:

extension CaseIterable {
    static func randomElement() -> AllCases.Element {
        guard Self.allCases.count > 0 else {
            fatalError("There must be at least one case in the enum")
        }
        return Self.allCases.randomElement()!
    }
}

This way any enum which conforms to CaseIterable has the function automatically

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM