skip to Main Content

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

Answers


  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.

    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
    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)
      }!
    }
    
    Login or Signup to reply.
  3. 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
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search