【问题标题】:Generate Array of Unique Random Numbers within Inclusive Range生成包含范围内的唯一随机数数组
【发布时间】:2015-12-22 18:42:12
【问题描述】:

我正在尝试在 Apple Swift (iOS) 中编写一个函数,该函数将生成任何给定数量的唯一随机数,这些随机数在给定的包含范围内,比如 0 到 10 之间。所以如果我说我想要 5 个唯一的随机数在 0 到 10 之间,它会返回一个包含 [7, 10, 2, 3, 0] 或 [7, 10, 2, 8, 0] 等的数组。

我正在处理这部分:

// Returns an array of unique numbers
func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32) -> [Int] {

    var uniqueNumbers = [Int]()

    while uniqueNumbers.count < numberOfRandoms {

        let randomNumber = Int(arc4random_uniform(maxNum + 1)) + minNum
        var found = false

        for var index = 0; index < uniqueNumbers.count; ++index {
                if uniqueNumbers[index] == randomNumber {
                    found = true
                    break
                }
        }

        if found == false {
            uniqueNumbers.append(randomNumber)
        }

    }

    return uniqueNumbers
}

print(uniqueRandoms(5, minNum: 0, maxNum: 10))

现在我想添加将我不想要的范围内的单个号码列入黑名单的功能。假设我仍然想要 5 个介于 0 和 10 之间的唯一随机数,但我不希望它包含 8。

这部分会导致无限循环(25% 以上的时间或更多),我不知道为什么?这是我所拥有的:

var blackListNum = 8

// Returns an array of unique numbers
func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32, checkBlackList: Bool = false) -> [Int] {

    var uniqueNumbers = [Int]()

    while uniqueNumbers.count < numberOfRandoms {

        let randomNumber = Int(arc4random_uniform(maxNum + 1)) + minNum
        var found = false

        for var index = 0; index < uniqueNumbers.count; ++index {
            if checkBlackList == false {
                if uniqueNumbers[index] == randomNumber {
                    found = true
                    break
                }
            } else {
                if uniqueNumbers[index] == randomNumber || uniqueNumbers[index] == blackListNum  {
                    found = true
                    break
                }
            }
        }

        if found == false {
            uniqueNumbers.append(randomNumber)
        }

    }

    return uniqueNumbers
}

print(uniqueRandoms(5, minNum: 0, maxNum: 10, checkBlackList: true))

我知道我的功能远非高效,因为我刚刚开始学习 Swift,但我想保持它的相似性,因为我想了解它是如何工作的。我不想简单地复制和粘贴别人的更有效的解决方案而不理解它。我刚刚学习了变量、常量、if、while、for 等语句和其他基础知识,并希望继续学习。

【问题讨论】:

    标签: arrays swift random swift2


    【解决方案1】:

    基于范围和集合的扩展。不能很好地扩展,但适用于小范围。

    extension Int {
      static func randomV(in range: ClosedRange<Int>, excluding usedRange: inout Set<Int>) -> Int
      {
        if usedRange.count == range.upperBound - 1 {
          usedRange = []
        }
        var r = Int.random(in: Range(uncheckedBounds: (range.lowerBound, range.upperBound)))
        while usedRange.contains(r) {
          r = Int.random(in: Range(uncheckedBounds: (range.lowerBound, range.upperBound)))
        }
        if usedRange.count < range.upperBound - 1 {
          usedRange.insert(r)
        }
        return r
      }
    }
    

    你需要调用的看起来像这样......

    var usedPool = Set<Int>()
    for loop in 0..<24 {
      let rnd = Int.randomV(in: 1...4, excluding: &usedPool)
    }
    

    这也是一种替代方法,它更具确定性,因为您可以查看 shuffled 函数的性能,以了解这对于更大的集合会有多糟糕。

    extension Int {
      static func randomX(randomSet:Set<Int>, working usedRange: inout Set<Int>) -> Int {
      if usedRange.isEmpty {
       usedRange = randomSet
      }
      let r = usedRange.first!
      usedRange.remove(at: usedRange.startIndex)
      usedRange = Set(usedRange.shuffled())
      return r
     }
    }
    

    你用这个来称呼它...

    let randomPool:Set = [1,2,3,4]
    var usedPool = randomPool
    for i in 0...10 {
    let foo = Int.randomX(randomSet: randomPool, working:&usedPool)
      print("foo ",foo)
    }
    

    【讨论】:

      【解决方案2】:

      您可以使用 Set 存储所有随机数,直到达到预期的随机数,让您的生活更轻松:

      func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32) -> [Int] {
          var uniqueNumbers = Set<Int>()
          while uniqueNumbers.count < numberOfRandoms {
              uniqueNumbers.insert(Int(arc4random_uniform(maxNum + 1)) + minNum)
          }
          return uniqueNumbers.shuffled()
      }
      
      print(uniqueRandoms(numberOfRandoms: 5, minNum: 0, maxNum: 10))
      
      func uniqueRandoms(numberOfRandoms: Int, minNum: Int, maxNum: UInt32, blackList: Int?) -> [Int] {
          var uniqueNumbers = Set<Int>()
          while uniqueNumbers.count < numberOfRandoms {
              uniqueNumbers.insert(Int(arc4random_uniform(maxNum + 1)) + minNum)
          }
          if let blackList = blackList {
              if uniqueNumbers.contains(blackList) {
                  while uniqueNumbers.count < numberOfRandoms+1 {
                      uniqueNumbers.insert(Int(arc4random_uniform(maxNum + 1)) + minNum)
                  }
                  uniqueNumbers.remove(blackList)
              }
          }
          return uniqueNumbers.shuffled()
      }
      
      uniqueRandoms(numberOfRandoms: 3, minNum: 0, maxNum: 10, blackList: 8)  // [0, 10, 7]
      

      【讨论】:

        【解决方案3】:
        // 1st version where I only blacklisted 8
        var randomNumbers = [Int]()
        for _ in 1...7 {
            var number = Int(arc4random_uniform(8))+1
            while  randomNumbers.contains(number) || number == 8{
                number = Int(arc4random_uniform(8))+1
            }
            randomNumbers.append(number)
        }
        print(randomNumbers)
        
        // 2nd version where I created an array of blacklisted numbers
        var randomNumbers = [Int]()
        var blackList = [8, 5, 2, 7]
        for _ in 1...3 {
            var number = Int(arc4random_uniform(10))+1
            while  randomNumbers.contains(number) || blackList.contains(number){
                number = Int(arc4random_uniform(10))+1
            }
            randomNumbers.append(number)
        }
        print(randomNumbers)
        

        我创建了 2 个空数组“randomNumbers”和“blackList”,然后设置了一个 for 循环语句。 在循环语句中,我初始化变量“number”。我将“number”分配给一个随机数,然后我使用 while 语句和 .contians 检查“number”是否已经在数组“randomNumbers”中,或者“number”是否在数组“blackList”中,如果任一数组包含"number," "number 被重新分配,直到两个数组都不包含 "number"。然后我 randomNumbers.append(number) 将 "number" 附加到 "randomNumbers" 中,然后循环重新开始。

        【讨论】:

          【解决方案4】:

          Swift 4.0 中的解决方案和性能细分

          我最近发现自己需要解决这个问题的解决方案,但没有黑名单,我在这个页面看到了答案,也在this question,但我对表现的数量是何时选择的非常大,并且在选择大量数字时(例如,您选择了整个池中超过 50% 的数字)。

          所以我尝试了一些解决方案。

          第一种是随机选择一个数字,检查该数字是否已经被选择过,然后选择一个不同的数字或将其添加到数字列表中。

          func randomNumber(between lower: Int, and upper: Int) -> Int {
              return Int(arc4random_uniform(UInt32(upper - lower))) + lower
          }
          
          func generateRandomUniqueNumbers1(forLowerBound lower: Int, andUpperBound upper:Int, andNumNumbers iterations: Int) -> [Int] {
              guard iterations <= (upper - lower) else { return [] }
              var numbers: [Int] = []
              (0..<iterations).forEach { _ in
                  var nextNumber: Int
                  repeat {
                      nextNumber = randomNumber(between: lower, and: upper)
                  } while numbers.contains(nextNumber)
                  numbers.append(nextNumber)
              }
              return numbers
          }
          

          第二种解决方案与 vacawama 建议的解决方案几乎相同。您首先加载一个包含所有可用数字的数组,随机选择一个,然后将其从可用数组中删除,然后将其添加到您的 numbers 数组中。重复任意数量的数字。

          func generateRandomUniqueNumbers2(forLowerBound lower: Int, andUpperBound upper:Int, andNumNumbers iterations: Int) -> [Int] {
              guard iterations <= (upper - lower) else { return [] }
              var indices: [Int] = (lower..<upper).sorted()
              var numbers: [Int] = []
              (0..<iterations).forEach { _ in
                  let nextNumberIndex = randomNumber(between: 0, and: indices.count)
                  let nextNumber: Int = indices[nextNumberIndex]
                  indices.remove(at: nextNumberIndex)
                  numbers.append(nextNumber)
              }
              return numbers
          }
          

          第三种解决方案是对第一种解决方案的改编,以解决数组在其中查找元素速度慢的问题。通过将存储的 numbers 数组更改为 Set,该操作应该更快。

          func generateRandomUniqueNumbers3(forLowerBound lower: Int, andUpperBound upper:Int, andNumNumbers iterations: Int) -> [Int] {
              guard iterations <= (upper - lower) else { return [] }
              var numbers: Set<Int> = Set<Int>()
              (0..<iterations).forEach { _ in
                  let beforeCount = numbers.count
                  repeat {
                      numbers.insert(randomNumber(between: lower, and: upper))
                  } while numbers.count == beforeCount
              }
              return numbers.map{ $0 }
          }
          

          我很确定解决方案 1 在您有 100 个数字可供选择并且您想要 90 个唯一的数字的情况下会陷入困境。当您选择第 80 个数字时,您只有 20% 的机会选择尚未选择的数字。如果你有 5000 个数字并且你仍然想要其中的 90%,那么它的扩展性真的很差。

          我认为解决方案 2 会更好地处理它,但我不确定从大型数组中删除这么多值会对性能造成什么样的影响。

          我不确定如何解决方案 3。我的猜测可能是中间的某个地方。

          我设置了 XCTest 来测量两种算法在不同负载条件下的一些性能。有两个参数:人口和密度。人口是可供选择的数字的总数,密度是我们想要抽出的人口的百分比(即:人口为 80 表示有 80 个数字可供选择,密度为 50% 表示我们要选择从 80 个人口中随机抽取 40 个唯一数字)。

          我对 3 个不同种群(5、250 和 12,500)和密度值(10%、50% 和 90%)的每种组合进行了 9 次测试。根据测试完成的快慢,我调整了我执行的测试迭代次数(从一次通过到多达 2,500 次不等)。

          结果如下:

          解决方案 1

          (Population: 5;      Density: 10%; Iterations: 2,500): 0.0056s
          (Population: 250;    Density: 10%; Iterations: 50)   : 0.0046s
          (Population: 12,500; Density: 10%; Iterations: 10)   : 1.33s
          (Population: 5;      Density: 50%; Iterations: 2,500): 0.0131s
          (Population: 250;    Density: 50%; Iterations: 50)   : 0.0912s
          (Population: 12,500; Density: 50%; Iterations: 1)    : 4.09s
          (Population: 5;      Density: 90%; Iterations: 2,500): 0.0309s
          (Population: 250;    Density: 90%; Iterations: 10)   : 0.0993s
          (Population: 12,500; Density: 90%; Iterations: 1)    : 23s
          

          解决方案 2

          (Population: 5;      Density: 10%; Iterations: 2,500): 0.0184s
          (Population: 250;    Density: 10%; Iterations: 50)   : 0.0086s
          (Population: 12,500; Density: 10%; Iterations: 10)   : 0.103s
          (Population: 5;      Density: 50%; Iterations: 2,500): 0.0233s
          (Population: 250;    Density: 50%; Iterations: 50)   : 0.0125s
          (Population: 12,500; Density: 50%; Iterations: 1)    : 0.0209s
          (Population: 5;      Density: 90%; Iterations: 2,500): 0.0242s
          (Population: 250;    Density: 90%; Iterations: 10)   : 0.0046s
          (Population: 12,500; Density: 90%; Iterations: 1)    : 0.0278s
          

          解决方案 3

          (Population: 5;      Density: 10%; Iterations: 2,500): 0.00672s
          (Population: 250;    Density: 10%; Iterations: 50)   : 0.0024s
          (Population: 12,500; Density: 10%; Iterations: 10)   : 0.0148s
          (Population: 5;      Density: 50%; Iterations: 2,500): 0.0134s
          (Population: 250;    Density: 50%; Iterations: 50)   : 0.00769s
          (Population: 12,500; Density: 50%; Iterations: 1)    : 0.00789s
          (Population: 5;      Density: 90%; Iterations: 2,500): 0.0209s
          (Population: 250;    Density: 90%; Iterations: 10)   : 0.00397s
          (Population: 12,500; Density: 90%; Iterations: 1)    : 0.0163s
          

          比较

          (Case 1): Solution 1 is fastest; then 3; then 2
          (Case 2): Solution 3 is fastest; then 1; then 2
          (Case 3): Solution 3 is fastest; then 2; then 3
          (Case 4): Solution 1 is fastest; then 3; then 2
          (Case 5): Solution 3 is fastest; then 2; then 1
          (Case 6): Solution 3 is fastest; then 2; then 1
          (Case 7): Solution 3 is fastest; then 2; then 1
          (Case 8): Solution 3 is fastest; then 2; then 1
          (Case 9): Solution 3 is fastest; then 2; then 1
          

          结论

          正如我从第一个解决方案中所怀疑的那样,它确实陷入了人口众多和高密度的困境。当您没有那么多人口或仅选择 2 个数字时,它仍然很快,但在这些条件下,所有解决方案总体上都非常快。即使解决方案 1 可以比解决方案 2 或 3 更快地从 250 个总体中选择 25 个随机数,但实时差异也很小。

          但是,需要指出的是,如果您想要从非常大的人口中获得很少的唯一数字(即:从 12,500 个池中提取 2 个唯一数字),则解决方案 1 最快,比解决方案 3 快约 77%,并且两者都比解决方案 2 快几个数量级。对于我的具体情况,这更接近我几乎一直要做的事情,所以我可能会制作一个特定的函数,使用解决方案 1 从一个大池中专门选择 2 个唯一数字数据的。

          总体而言,解决方案 3 非常接近一个全方位的最佳算法。但是对于大型数据集,请根据您计划使用它们的目的考虑这些解决方案中的每一个。

          【讨论】:

          • 巨大的答案。谢谢。
          【解决方案5】:

          这么简单的方法就是:

            let UniqueIdNumber = CFUUIDCreateString(nil, CFUUIDCreate(nil))
          

          【讨论】:

          • UUID 不是数字。提问者显然使用整数作为他们的示例,而 UUID 虽然它们肯定是唯一且随机的,但对这个问题根本没有帮助。
          • 是的,你得到一个 UUID(像这样:“37165960-ED1F-4271-92A0-41D9730CF250”),它是一个字符串,而不是一个整数。
          • 我想你不知道也不理解 UUID 的含义!请参阅页面-> en.wikipedia.org/wiki/Universally_unique_identifier 您不能用数字创建唯一标识符!在某些时候它不会是独一无二的!您在上面粘贴的是潜在的唯一 ID。创建 UUID 的方法很简单!所以,在你给任何 cmets -1 之前,请确保你对这个领域非常了解。
          • 我非常了解 UUID 是什么。我很惊讶你的回答还没有任何反对意见。从最初的问题开始:“我正在尝试在 Apple Swift (iOS) 中编写一个函数,它将生成任何给定数量的唯一随机数,这些随机数在给定的包含范围内,比如 0 到 10 之间。”提问者想要 NUMBERS,它们是整数。 UUID 是一个字符串。仅此一项就意味着 UUID 不可能是他正在寻找的东西。其次,它需要在一个包容的范围内。 UUID 不是有序序列的一部分。也许您认为您在回答不同的问题?
          【解决方案6】:

          这是我的解决方案。 SwiftStub:

          extension Array {
              func shuffle() -> Array<Element> {
                  var newArray = self
          
                  for i in 0..<newArray.count {
                      let j = Int(arc4random_uniform(UInt32(newArray.count)))
                      guard i != j else { continue }
                      swap(&newArray[i], &newArray[j])
                  }
          
                  return newArray
              }
          }
          
          func uniqueRandoms(count: Int, inRange range: Range<Int>, blacklist: [Int] = []) -> [Int] {
              var r = [Int](range)
                  .filter{ !blacklist.contains($0) }
                  .shuffle()
          
              return Array(r[0..<count])
          }
          
          let x = uniqueRandoms(5, inRange: 1...10000)
          let y = uniqueRandoms(5, inRange: 1...10, blacklist: [2,4,6,8,10])
          print(x)
          print(y)
          

          filter 过滤掉黑名单上的号码。

          shuffle 是添加到Array 类的扩展。如果需要,您可以将其实现为单独的函数。

          return Array(r[0..&lt;count]) 从现有数组的Slice 创建一个新数组。

          当列表小于要求的count 时,这有一个潜在的索引越界错误。例如,这些会崩溃:

          let a = uniqueRandoms(10, inRange: 1...5)
          let b = uniqueRandoms(3, inRange: 1...5, blacklist: [1,2,3,4])
          

          处理留给 OP 的练习。

          【讨论】:

          • swift 4.2 将 Range 替换为 ClosedRange
          【解决方案7】:

          一种直接的方法是创建一个可供选择的数字数组,然后在选择时删除这些数字:

          // create an array of 0 through 10
          var nums = Array(0...10)
          
          // remove the blacklist number
          nums.removeAtIndex(nums.indexOf(8)!)
          
          var randoms = [Int]()
          for _ in 1...5 {
              let index = Int(arc4random_uniform(UInt32(nums.count)))
              randoms.append(nums[index])
              nums.removeAtIndex(index)
          }
          

          这种方法的优点是您只需要在数组中生成任意数量的随机数即可。由于您每次都从仍然可用的数字中进行选择,因此您无需检查是否已经有随机值。

          【讨论】:

            猜你喜欢
            • 2011-08-02
            • 1970-01-01
            • 2014-05-15
            • 2013-12-14
            • 2018-12-17
            • 2010-12-24
            • 1970-01-01
            • 2018-04-08
            • 1970-01-01
            相关资源
            最近更新 更多