如何增加或减少数组中某项被选中的概率

How to increase or decrease the probability of an item of an array being picked

假设我有这样一组数字:

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

如果我想从数组中打印一个随机数,我可以这样做:

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

以上行将从我的数组中return一个随机值。

我想做的是,为数组中的每个值设置一个概率。例如:

- 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% 

最好的方法是什么?任何指导或建议将不胜感激。我面临的这个问题是在 swiftUI 中。

1 添加到数组中 10 次,添加 2 20 次,依此类推。您的 Array 将有 100 个元素,因此您将控制每个元素的概率。

如果您的值总是 5% 的倍数,您可以使用包含 20 个元素的数组并按比例添加值。

比 UI 问题更多的是数学问题,但是:

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

并证明它:

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

  1. 创建标准化加权范围。
[ 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) {
        [=12=].upperBound..< / sum + [=12=].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. 样本。 (二进制搜索会比 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
    [=15=].contains(random)
  }!
}

效率更高:

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
}