How do I generate a random number not including one without using a while loop?

Updated for Swift 5.1

Excluding 1 value

var nums = [Int](1...100)
nums.remove(at: 42)

let random = Int(arc4random_uniform(UInt32(nums.count)))
print(nums[random])

Excluding multiple values

This extension of Range does provide a solution when you want to exclude more than 1 value.

extension ClosedRange where Element: Hashable {
    func random(without excluded:[Element]) -> Element {
        let valid = Set(self).subtracting(Set(excluded))
        let random = Int(arc4random_uniform(UInt32(valid.count)))
        return Array(valid)[random]
    }
}

Example

(1...100).random(without: [40,50,60])

I believe the computation complexity of this second solution is O(n) where n is the number of elements included in the range.

The assumption here is the no more than n excluded values are provided by the caller.


appzYourLife has some great general purpose solutions, but I want to tackle the specific problem in a lightweight way.

Both of these approaches work roughly the same way: Narrow the range to the random number generator to remove the impossible answer (99 answers instead of 100), then map the result so it isn't the illegal value.

Neither approach increases the probability of an outcome relative to another outcome. That is, assuming your random number function is perfectly random the result will still be random (and no 2x chance of 43 relative to 5, for instance).

Approach 1: Addition.

Get a random number from 1 to 99. If it's greater than or equal to the number you want to avoid, add one to it.

func approach1()->Int {
    var number = Int(arc4random_uniform(99)+1)
    if number >= 42 {
        number = number + 1
    }
    return number
}

As an example, trying to generate a random number from 1-5 that's not 3, take a random number from 1 to 4 and add one if it's greater than or equal to 3.

  • rand(1..4) produces 1, +0, = 1
  • rand(1..4) produces 2, +0, = 2
  • rand(1..4) produces 3, +1, = 4
  • rand(1..4) produces 4, +1, = 5

Approach 2: Avoidance.

Another simple way would be to get a number from 1 to 99. If it's exactly equal to the number you're trying to avoid, make it 100 instead.

func approach2()->Int {
    var number = Int(arc4random_uniform(99)+1)
    if number == 42 {
        number = 100
    }
    return number
}

Using this algorithm and narrowing the range to 1-5 (while avoiding 3) again, we get these possible outcomes:

  • rand(1..4) produces 1; allowed, so Result = 1
  • rand(1..4) produces 2, allowed, so Result = 2
  • rand(1..4) produces 3; not allowed, so Result = 5
  • rand(1..4) produces 4, allowed, so Result = 4