Returning members of an Enum with a given frequency

I have an enum, and I am trying to randomly return one of the members of the enum where each member can be given a frequencey of how often it is chosen (e.g. a member with a percentage of 0.05 would be returned 5% of the time).


I will post my best solution below, but I am hoping someone else has a better swift-2-ier way of doing it (e.g. filter/map/reduce, etc...), or a simpler solution.


Some notes:

  • ListableEnum is a protocol which adds the allMembers() function in an extension (it returns an array of all members)
  • init?(index:) is required by the ListableEnum to work its magic
  • random() is added to Double via extension and it returns a Double between 0 and 1 (inclusive)
  • I make an assumption that the frequencies add to 1.0


enum Test:Int,ListableEnum {
     case A,B,C

     init?(index:Int){self.init(rawValue:index)}

     var frequency:Double {
          switch self{
          case .A: return 0.1  //10%
          case .B: return 0.85 //85%
          case .C: return 0.05 //5%
          }
     }

     static var randomMember:Test{
          let allM = Test.allMembers()
          let rnd = Double.random()
          var total = 0
          for m in allM {
               total += m.frequency
               if total >= rnd {
                    return m
               }
          }
          return allM.last!
     }
}


Feel free to tell me I am being an idiot here (as long as it is accompanied by constructive feedback)

I'm having difficulties understanding what the fundamental requirements are, do you have to use enums for example, why not a Set (whose elements can be enumerated and conform to some protocol that requires them to have a frequency/probability)?


Can you describe what it is that you want to do in more general terms, with some context and without any specific implementation details?

I am experimenting with generating believable characters/clues for a little mystery game (mostly as an exercise for learning Swift 2).


I am a big fan of mysteries, and most games that have randomly generated mysteries have problems with making rare things too common, and also bad correlations (e.g. a 6'5" man with size 4 shoes). I would like my characters to have characteristics (e.g. eye color, blood type, etc...) that generally match real world numbers. It doesn't have to be perfect but the 2 main characteristics that I would like:

  • Rare things should only show up rarely (and common ones commonly)
  • Correlation between factors should be believable

I am currently handling the second by having a fixed order in which the characteristics are chosen (Gender then Height, etc...), and altering the frequencies of later stages based on the earlier results. I am open to better solutions for that as well. I am also taking into account familial relations when generating relatives... but that is another topic.

As an example, here is my frequency function for fingerprint patterns. Only about 5% of fingerprints are arches, so an arch pattern should only be chosen ~5% of the time.

var likelyhood:Double {
            switch self{
            case .RadialLoop,.UlnerLoop: return (0.60/2.0)
            case .PlainWhorl,.CentralPocketWhorl,.DoubleLoop,.AccidentalWhorl: return (0.35/4.0)
            case .PlainArch,.TentedArch: return (0.05/2.0)
            }
        }

Not sure if this is "better", but I feel it's a simpler way to get a % based result:


import Foundation
enum Test {
    case A
    case B
    case C
}
var tests:[Test] = Array<Test>(count: 10, repeatedValue: Test.A)
tests.extend(Array<Test>(count: 85, repeatedValue: Test.B))
tests.extend(Array<Test>(count: 5, repeatedValue: Test.C))
print(tests[random() % tests.count])


The idea is just that we fill a 100 element array with the appropriate amount of each item, and select one at random to return.

enum MyEnumeration : Double {
    // Cases are any values between 0.0 and 1.0, with 0.1 intervals
    // Sum need not be 1.0. It is 0.9 in this case
    case A = 0.2, B = 0.6, C = 0.1
   
    // Raw values per member
    static let memberValues = stride(from: 0.0, through: 1.0, by: 0.1).filter{MyEnumeration(rawValue: $0) != nil}
   
    // Sum of raw values
    static let memberSum = memberValues.reduce(0.0, combine: +)
   
    // Multiplier offset from a true 1.0 sum
    static let memberMultiplier = 1.0 / memberSum
   
    // Members -- may not be in expected order
    static let members = memberValues.flatMap{MyEnumeration(rawValue: $0)}
   
    // Expected frequency per member based on percent distribution
    static let expectedFrequencies = zip(members, memberValues.map{$0 * memberMultiplier}).map{($0, $1)}


    // Return a probability-weighted member
    static var randomMember : MyEnumeration {
        var values = ArraySlice(memberValues)
        var roll = memberSum * (Double(arc4random()) / Double(UINT32_MAX)) // weight by sum
        repeat {
            guard let first = values.first else {fatalError()}
            guard let firstItem = MyEnumeration(rawValue: first) else {fatalError()}
            if roll < first {return firstItem}
            roll -= first; values = dropFirst(values)
        } while !values.isEmpty
        guard let defaultEnumeration = MyEnumeration(rawValue: 0.0) else {fatalError()}
        return defaultEnumeration
    }
}


// Monte Carlo the results
var aCount = 0; var bCount = 0; var cCount = 0
let num = 1000
for _ in 0...num {
    switch MyEnumeration.randomMember {
    case .A: aCount++
    case .B: bCount++
    case .C: cCount++
    }
}


MyEnumeration.members // .C, .A, .B, not in expected order
print(MyEnumeration.expectedFrequencies)
print(zip(MyEnumeration.members, [cCount, aCount, bCount].map{Double($0) / Double(num)}).map{($0, $1)})
Returning members of an Enum with a given frequency
 
 
Q