0

Lets say I have an array of numbers like this:

// Create Array Of Numbers
let numbers = ["1","2","3","4","5"]

If I want to print a random number from the array, I can do something like:

pickedNumber = Int.random(in: 0...numbers.count - 1)

The above line will return a random value from my array.

What I would like to do is, set a probability for each value in the array. For example:

- Chance of 1 being picked at 10%
- Chance of 2 being picked at 20%
- Chance of 3 being picked at 30%
- Chance of 4 being picked at 35%
- Chance of 5 being picked at 5% 

What's the best approach for this? Any guidance or advice would be appreciated. This problem I am facing is in swiftUI.

4 Answers4

1

more of a mathematical question than an UI question, but nevertheless:

let probs = [
    1 : 10,
    2 : 20,
    3 : 30,
    4 : 35,
    5 : 5
]

func randomWithProbability(distribution: [Int : Int]) -> Int {
    
    var distributionArray: [Int] = []
    distribution.forEach { (key: Int, value: Int) in
        let new = Array(repeating: key, count: value)
        distributionArray.append(contentsOf: new)
    }
    let r = Int.random(in: 0..<distributionArray.count)
    return distributionArray[r]
    
}

and to prove it:

struct ContentView: View {
    
    private var results: [Int]
    
    init() {
        results = [0,0,0,0,0]
        for _ in 0..<1000 {
            let i = randomWithProbability(distribution: probs)
            results[i-1] += 1
        }
    }
    
    var body: some View {
        
        VStack(alignment: .leading) {
            ForEach(results.indices) { i in
                HStack {
                    Text("\(i)")
                    Color.blue
                        .frame(width: CGFloat(results[i]), height: 40)
                }
            }
        }
    }
}

enter image description here

ChrisR
  • 9,523
  • 1
  • 8
  • 26
0
  1. Create normalized weighted ranges.
[ 0.0..<0.1,
  0.1..<0.3,
  0.3..<0.6,
  0.6..<0.95,
  0.95..<1.0
]
let normalizedWeightedRanges = [10, 20, 30, 35, 5].normalizedWeightedRanges
import Algorithms 

public extension Sequence where Element: FloatingPoint {
  /// Normalized (`0..<1`) representations of the elements' weights within their sum.
  var normalizedWeightedRanges: [Range<Element>] {
    guard let sum = sum else {
      return []
    }
  
    return .init(
      reductions(0..<0) {
        $0.upperBound..<$1 / sum + $0.upperBound
      }.dropFirst()
    )
  }
}
public extension Sequence where Element: AdditiveArithmetic {
  var sum: Element? { reduce(+) }
}
public extension Sequence {
  /// - Returns: `nil` If the sequence has no elements, instead of an "initial result".
  func reduce(
    _ nextPartialResult: (Element, Element) throws -> Element
  ) rethrows -> Element? {
    var iterator = makeIterator()
    return try iterator.next().map { first in
      try IteratorSequence(iterator).reduce(first, nextPartialResult)
    }
  }
}

  1. Sample. (Binary search would be better than firstIndex.)
/// - precondition: `normalizedWeightedRanges` is the result of `Sequence.normalizedWeightedRanges`
func randomIndex<Float: BinaryFloatingPoint>(
  inNormalizedWeightedRanges normalizedWeightedRanges: [Range<Float>]
) -> Int
where Float.RawSignificand: FixedWidthInteger {
  normalizedWeightedRanges.firstIndex { [random = Float.random(in: 0..<1)] in
    $0.contains(random)
  }!
}
0

more efficient:

let results = getResults(input:[0.1, 0.3, 0.6, 0.95,1])

func getResults(input: [Float]) -> [Int] {
    var results: [Int] = [Int](0..<input.count)
    for _ in 0..<iteration {
        let random = Float.random(in: 0...1)
        for i in input.enumerated() {
            if random <= i.element {
                results[i.offset] += 1
                break
            }
        }
    }
    return results
}
Simone Pistecchia
  • 2,746
  • 3
  • 18
  • 30
-1

Add 1 10 times to your array, 2 20 times, and so on. Your Array will have 100 elements so you will control the probability for each element.

If your values are always going to be multiples of 5% you can use an array of 20 elements and add a proportional number of values.

EmilioPelaez
  • 18,758
  • 6
  • 46
  • 50
  • Is there a more efficient way to do this? Or is this the best approach? Essentially, I am creating a slots app, and I want the probability of certain images to come up less often. –  Feb 01 '22 at 23:48
  • It's not the most efficient in terms of building the initial array or in terms of memory, but getting the value should `O(1)`, so you can't get better than that. An array of 100 values should also have no impact on your app, it'll probably be smaller than the smallest of your graphics. – EmilioPelaez Feb 02 '22 at 00:02