Skip to content

Instantly share code, notes, and snippets.

@erica
Last active February 19, 2019 23:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/627531342c0394205219 to your computer and use it in GitHub Desktop.
Save erica/627531342c0394205219 to your computer and use it in GitHub Desktop.
Probability-Weighted Enumeration
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
See https://forums.developer.apple.com/message/11944
import UIKit
enum MyEnumeration : Int {
case A = 10, B = 85, C = 5
// Raw values per member
static let memberValues = stride(from: 0, through: 100, by: 1).filter{
MyEnumeration(rawValue: $0) != nil}
// Sum of raw values
static let memberSum = memberValues.reduce(0, combine: +)
// Multiplier offset from a true 1.0 sum
static let memberMultiplier = 1.0 / Double(memberSum)
// Members -- sorted by raw values
static let members = memberValues.flatMap{MyEnumeration(rawValue: $0)}
// Expected frequency per member based on percent distribution
static let expectedFrequencies = zip(members, memberValues.map{Double($0) * memberMultiplier}).map{($0, $1)}
// Return a probability-weighted member
static var randomMember : MyEnumeration {
var values = ArraySlice(memberValues)
var roll = Double(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 < Double(first) {return firstItem}
roll -= Double(first); values = dropFirst(values)
} while !values.isEmpty
guard let defaultEnumeration = MyEnumeration(rawValue: 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("Expected ", false)
print(MyEnumeration.expectedFrequencies)
print("Actual ", false)
print(zip(MyEnumeration.members, [cCount, aCount, bCount].map{Double($0) / Double(num)}).map{($0, $1)})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment