Swift: Chose a random enumeration value

2020-02-19 08:16发布

i am trying to randomly chose a enum value, this is my current attempt:

enum GeometryClassification {

    case Circle
    case Square
    case Triangle
    case GeometryClassificationMax

}

and the random selection:

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

this however fails miserably.

i get errors like:

'GeometryClassification' is not convertible to 'UInt32'

any ideas on how to solve that?

6条回答
We Are One
2楼-- · 2020-02-19 08:26

Swift has gained new features since this answer was written that provide a much better solution — see this answer 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")
}
查看更多
别忘想泡老子
3楼-- · 2020-02-19 08:26

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.

查看更多
别忘想泡老子
4楼-- · 2020-02-19 08:31

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)!
    }
}
查看更多
Rolldiameter
5楼-- · 2020-02-19 08:33

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)!
    }
}
查看更多
forever°为你锁心
6楼-- · 2020-02-19 08:41

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
}

"Unlike C and Objective-C, Swift enumeration members are not assigned a default integer value when they are created." - as per this page. 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)
查看更多
We Are One
7楼-- · 2020-02-19 08:44

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]
    }
}
查看更多
登录 后发表回答