从范围内生成 Swift 中的唯一随机数列表

Generate list of unique random numbers in Swift from range

我想写一个函数,它应该 return 整数列表。整数列表大小和最大随机数将由用户定义,用户将通过函数参数传递。我的特殊要求是,integer into list不能冗余
我使用 Array 的方法:

func getRandomNumbers(maxNumber: Int, listSize: Int)-> [Int]{
    var randomNumbers = [Int]()
    for _ in 1...listSize{
        let randomNumber = Int(arc4random_uniform(UInt32(listSize)))
        randomNumbers.append(randomNumber)
    }
    return randomNumbers
}

问题:有时我会用这种方法得到重复的值。
我知道 swift Set 不包含重复值。

我使用 Set 的方法:

func getRandomNumbers(maxNumber: Int, listSize: Int)-> Set<Int>{
    var randomNumbers = Set<Int>()
    for _ in 1...listSize{
        let randomNumber = Int(arc4random_uniform(UInt32(listSize)))
        randomNumbers.insert(randomNumber)
    }
    return randomNumbers
} 

问题:有时 Set 大小小于用户定义的大小。

你只需要使用 while count is less than listSize 而不是 for 循环,而不是你需要将 maxNumber 加一而不是 listSize 传递给 arc4random_uniform:

func getRandomNumbers(maxNumber: Int, listSize: Int)-> Set<Int> {
    var randomNumbers = Set<Int>()
    while randomNumbers.count < listSize {
        let randomNumber = Int(arc4random_uniform(UInt32(maxNumber+1)))
        randomNumbers.insert(randomNumber)
    }
    return randomNumbers
}

getRandomNumbers(maxNumber: 10, listSize: 3)  // {5, 7, 0}

当您获得重复的随机数时会发生这种情况。您可以检查 randomNumbers.count 并生成随机数,直到达到 listSize.

,而不是生成 listSize 随机数

这里有2个问题:

  1. 您没有生成足够的数字。您需要不断生成随机数,直到您的集合足够大:

    func getRandomNumbers(maxNumber: Int, listSize: Int)-> [Int] {
        var randomNumbers = Set<Int>()
        while randomNumbers.count < listSize {
            let randomNumber = Int(arc4random_uniform(UInt32(maxNumber+1)))
            randomNumbers.insert(randomNumber)
        }
        return randomNumbers
    }
    
  2. 您通过将随机数置于 Set 选择的顺序中来使它们产生偏差,这是高度可预测的。您应该将您的数字附加到一个数组(以保持它们的顺序生成它们),同时仍然并行地利用一个集合,以实现快速重复数据删除:

    func getRandomNumbers(maxNumber: Int, listSize: Int)-> [Int] {
        precondition(listSize < maxNumber, "Cannot generate a list of \(listSize) unique numbers, if they all have to be less than \(maxNumber)")
    
        var randomNumbers = (array: [Int](), set: Set<Int>())
    
        while randomNumbers.set.count < listSize {
            let randomNumber = Int(arc4random_uniform(UInt32(maxNumber+1)))
            if randomNumbers.set.insert(randomNumber).inserted { // If the number is unique
                randomNumbers.array.append(randomNumber) // then also add it to the arary
            }
        }
    
        return randomNumbers.array
    }
    

还有另一种可能性。我不认为这是最好的,但在我看来 IndexSet 可能是解决问题的好方法(基本上,这个想法 nerd-sniped 我和我必须找到答案),这就是我想出的。

func getRandomNumbers(maxNumber: Int, listSize: Int)-> [Int]{
    guard maxNumber < listSize else { return [] }
    var indexSet = IndexSet(integersIn: 0...listSize)
    var randomInts = [Int]()

    while randomInts.count < maxNumber {
        guard let currentInt = indexSet.integerLessThanOrEqualTo(Int(arc4random_uniform(UInt32(listSize)))) else {
            continue
        }
        randomInts.append(currentInt)
        indexSet.remove(currentInt)
    }
    return randomInts
}

想法是创建一个 IndexSet 覆盖 0 到请求的最大值,然后从该集合中随机删除元素,直到你有足够的元素。它通过在使用时从集合中删除整数来避免重复。 IndexSet 实际上不是 Set,它是存储一堆唯一整数的有效方式,因此它获得了 Set 的唯一性,而无需实际创建包含所有内容的 Set范围内的整数。

Swift 5 版本实现

extension Int {

    static func getUniqueRandomNumbers(min: Int, max: Int, count: Int) -> [Int] {
        var set = Set<Int>()
        while set.count < count {
            set.insert(Int.random(in: min...max))
        }
        return Array(set)
    }

}

例如:

let uniqueNumbers = Int.getUniqueRandomNumbers(min: 1000, max: 1500, count: 10)

示例结果:

[1454, 1105, 1305, 1176, 1498, 1127, 1310, 1209, 1373, 1198]