Как выбрать случайное значение перечисления

Я пытаюсь случайным образом выбрать значение перечисления:

enum GeometryClassification {

    case Circle
    case Square
    case Triangle
    case GeometryClassificationMax

}

и случайный выбор:

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

но это не удается.

Я получаю такие ошибки, как:

'GeometryClassification' is not convertible to 'UInt32'

Как мне решить эту проблему?


person Sebastian Flückiger    schedule 08.10.2014    source источник


Ответы (9)


С момента написания этого ответа Swift получил новые функции, которые обеспечивают гораздо лучшее решение - см. "Как выбрать случайное значение перечисления".


Я не без ума от вашего последнего случая - похоже, вы включаете .GeometryClassificationMax исключительно для включения случайного выбора. Вам нужно будет учитывать этот дополнительный случай везде, где вы используете оператор switch, и он не имеет семантического значения. Вместо этого статический метод на enum мог бы определить максимальное значение и вернуть случайный случай, и был бы намного более понятным и удобным в обслуживании.

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)!
    }
}

И теперь вы можете исчерпать enum в операторе switch:

switch GeometryClassification.randomGeometry() {
case .Circle:
    println("Circle")
case .Square:
    println("Square")
case .Triangle:
    println("Triangle")
}
person Nate Cook    schedule 08.10.2014
comment
вы правы - в конце концов я выбрал именно такой подход, чтобы сделать его более читабельным. спасибо за усилия. - person Sebastian Flückiger; 08.10.2014
comment
++maxValue будет устаревшим в Swift 3. Как бы вы исправили это в своем коде? - person Cesare; 02.06.2016
comment
@Cesare: Вы можете переместить инкремент внутри цикла while. - person Nate Cook; 02.06.2016
comment
Спасибо. Есть идеи, как сделать этот код многоразовым? В моем приложении есть три перечисления, и я использую этот код трижды. Я подумал о создании класса (потому что вы не можете расширить перечисление с помощью расширения?). - person Cesare; 03.06.2016

В Swift на самом деле существует протокол для перечислений под названием CaseIterable, который, если вы добавите его в свое перечисление, вы можете просто ссылаться на все случаи как на коллекцию с .allCases следующим образом:

enum GeometryClassification: CaseIterable {

    case Circle
    case Square
    case Triangle

}

а затем вы можете .allCases, а затем .randomElement(), чтобы получить случайный

let randomGeometry = GeometryClassification.allCases.randomElement()!

Принудительное разворачивание требуется, потому что существует вероятность того, что перечисление не будет иметь случаев и, таким образом, randomElement() вернет nil.

person Pharphuf7nik    schedule 03.12.2018
comment
Важны строчные буквы и отсутствие скобок в .allCases. Я случайно использовал GeometryClassification.AllCases().randomElement()! (спасибо, автозаполнение), который совершенно другой и привел к ошибке выполнения. - person Darrell Root; 25.07.2019

Поскольку вы находитесь внутри класса enum, указание на метод random() наивысшего значения явным образом избавит от необходимости каждый раз их подсчитывать:

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)!
    }
}
person stevex    schedule 12.05.2015
comment
Это не работает в Swift 1.2. Он хочет, чтобы перечислению было присвоено rawValue. В свою очередь, это означает, что он работает только с перечислениями с типом Int rawValue. - person BadmintonCat; 14.05.2015
comment
В моем коде была опечатка, я возвращал неправильный тип. - person stevex; 14.05.2015

Для Swift 5 существует "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)
    }
}
person Andy    schedule 22.02.2020

Вам нужно назначить необработанный тип вашему перечислению. Если вы используете целочисленный тип, то значения случая перечисления будут автоматически сгенерированы, начиная с 0:

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

Согласно перечислениям:

«В отличие от C и Objective-C, членам перечисления Swift не присваивается целочисленное значение по умолчанию при их создании».

Указание целочисленного типа позволяет ему генерировать значения обычным способом.

Затем вы можете сгенерировать случайное значение следующим образом:

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

Это ужасно уродливый вызов, и все эти вызовы fromRaw и toRaw довольно неэлегантны, поэтому я бы действительно рекомендовал сначала сгенерировать случайный UInt32, который находится в нужном вам диапазоне, а затем создать GeometryClassification из этого значения:

GeometryClassification.fromRaw(someRandomUInt32)
person Ideasthete    schedule 08.10.2014
comment
В этом случае вам не нужно назначать конкретные значения; они будут автоматически созданы, начиная с 0. Часть, которую вы процитировали из документов, только в том случае, если enum не имеет целочисленного необработанного типа. Далее в том же документе: когда целые числа используются для необработанных значений, они автоматически увеличиваются, если для некоторых членов перечисления не указано значение. - person Mike S; 08.10.2014
comment
@ SebastianFlückiger, вам нужно создать GeometryClassification из необработанного значения, возвращенного из arc4random: let shapeGeometry = GeometryClassification(rawValue: ( arc4random() % GeometryClassification.GeometryClassificationMax.rawValue )) - person Mike S; 08.10.2014
comment
@MikeS Я не уверен, что UInt32 - это то же самое, что «целое число» в этом контексте. Я бы предположил, что это так, но я бы сказал, что если перечисление будет использоваться таким образом, чем больше конкретики, тем лучше. Тем не менее, я отредактирую свой ответ, чтобы отразить это. - person Ideasthete; 08.10.2014
comment
@ 0O0O0O0 Он автоматически сгенерирует значения с UInt32. Значения также должны начинаться с 0, если они будут правильно работать со значением, возвращаемым из arc4random (так как оно все равно используется в настоящее время). - person Mike S; 08.10.2014
comment
@ SebastianFlückiger См. Мои правки выше - это все, что вам нужно, чтобы все заработало. Сообщите мне, если у вас все еще есть проблемы. - person Ideasthete; 08.10.2014
comment
Благодарность! В конце концов справился с этим сам и остановился на том же решении :) Теперь доволен: P - person Sebastian Flückiger; 08.10.2014

Вы можете поместить все значения в массив и сгенерировать случайный,

extension GeometryClassification {

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

Вот мой дубль Swift 1.2:

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)!
    }
}
person BadmintonCat    schedule 14.05.2015

Я написал глобальное расширение, используя ответ Энди. Наслаждаться :)

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)
    }
}

Просто расширьте свое перечисление, чтобы оно соответствовало протоколу CaseIterable, и используйте следующее:

let state = YourEnum.random()
person Stanislav M.    schedule 04.03.2021

Проще всего создать глобальное расширение:

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()!
    }
}

Таким образом, любое перечисление, которое соответствует CaseIterable, автоматически имеет функцию

person OkiRules    schedule 11.04.2021