Let's say I want to generate a random number between 1 and 100, but I don't want to include 42. How would I do this without repeating the random method until it is not 42.
2 Answers
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.

- 58,465
- 13
- 121
- 148
-
1actually yours is much better, than the other one you amazed, because yours are more flexible to exclude various amount of numbers – if that is necessary. – holex Dec 17 '15 at 20:56
-
It's definitely more flexible. It's also much heavier, though, so if you just need what was originally asked for my answer is more performant. – Steven Fisher Feb 04 '16 at 19:59
-
@StevenFisher: Yes I find your solution very elegant and I already upvoted it :) – Luca Angeletti Feb 04 '16 at 20:44
-
Thanks, @appzYourLife. It's not your answer I object to. I really like it, actually. (Cool example of some Swift stuff I don't use yet.) It's just I've been staring at the comment above for a month trying to figure out why it bunches my shorts, and I finally figured it out. There's a trade off here, not just a "better." :) – Steven Fisher Feb 04 '16 at 21:06
-
1@StevenFisher: agreed, my answer does address a more general problem but for the exact question posted here your answer is very fast and optimized. Infact it does require `O(1)` time and `O(1)` space versus `O(n)` time and `O(n)` space required by mine. – Luca Angeletti Feb 04 '16 at 21:10
-
I am getting "Cannot use mutating member on immutable value: function call returns immutable value" when I try to use it with the Swift5 – timetraveler90 Dec 27 '19 at 11:00
-
@timetraveler90 Thanks for the comment, will have a look later today. – Luca Angeletti Dec 27 '19 at 11:16
-
@LucaAngeletti thank you for a really quick response – timetraveler90 Dec 27 '19 at 11:29
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

- 44,462
- 20
- 138
- 192
-
-
No, it doesn't. >=, not ==. The only way the result can be 43 is if the random roll is 42. If the random roll is 43, the result is turned into 44 and so on all the way up to 99 becoming 100. – Steven Fisher Dec 20 '15 at 15:40
-
I love both the answers here. I learned something from yours, @appzYourLife, as I'm not really a Swift guy yet. :) – Steven Fisher May 20 '16 at 01:14