I am trying to write a function in Apple Swift (iOS) that will generate any given amount of unique random numbers that are within a given inclusive range, say between 0 and 10. So if I say I want 5 unique random numbers between 0 and 10, it would return an array with [7, 10, 2, 3, 0] or [7, 10, 2, 8, 0], etc.
I have that part working with:
// 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))
Now I want to add the ability to blacklist a single number within that range that I don’t want. Say I still want 5 unique random numbers between 0 and 10 BUT I don’t want it to ever include 8.
That part causes an endless loop (25%+ of the time or more) and I can’t figure out why? Here’s what I have:
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))
I understand that my function is far from efficient because I am just starting to learn Swift but I want to keep it similar as I want to understand how it works. I don’t want to simply copy and paste someone else’s more efficient solution and not understand it. I have just learned variables, constants, if, while, for, etc. statements and the other basics and want to keep it to that.
You could make your live much easier using a Set to store all random numbers until you reach the expected number of randoms:
You will need this extension to shuffle the result:
Here's my solution. SwiftStub:
filter
filters out the numbers on the black list.shuffle
is an extension added to theArray
class. You implement it as a separate function if you want.return Array(r[0..<count])
creates a new array from aSlice
of the existing array.This has a potential index out of bound bug when the list is smaller than the
count
asked for. For examples, these will crash:Handling that is left as an exercise for the OP.
so much easy way is :
A straight forward approach is to create an array of numbers to choose from and then remove the numbers as you choose them:
The advantage of this method is that you only need to generate as many random numbers as you want values in your array. Since you are selecting from the numbers that are still available each time, you never have to check to see if you already have a random value.
Solutions and Performance breakdown in Swift 4.0
I recently found myself needing a solution to this very same problem, but without the blacklist, and I saw answers on this page and also over at this question, but I was concerned about performance when the set of numbers to choose from was very large and also when choosing a lot of numbers (like, to where you're choosing more than 50% of the numbers in the overall pool).
So I tried out a few solutions.
The first was the kind where you choose a number at random, check if the number has already been chosen before, and either choose a different one or add it to the list of numbers.
The second solution is pretty much the same as the one that vacawama is suggesting. You start by loading up an array of all the available numbers, choose one at random, and remove it from the available array, and add it to your numbers array. Repeat for as many numbers as you want.
The third solution was an adaptation of the first solution to address the fact that arrays are slow at finding elements within them. By changing the stored numbers array to a Set, that operation should be faster.
I was pretty sure that solution 1 would bog down in situations like where you have 100 numbers to choose from, and you want 90 unique ones. By the time you are choosing the 80th number, you only have a 20% chance of choosing one that hasn't been chosen yet. And it scales really badly if you have like 5000 numbers and you still want 90% of them.
I figured that solution 2 would handle it better, but I wasn't sure what kind of a performance hit removing so many values from a large array would have.
I wasn't sure what to make of solution 3. Probably somewhere in the middle was my guess.
I set up XCTest to measure some performance on both algorithms under different load conditions. There were 2 parameters: population and density. Population is the total number of numbers to choose from, and density is what percentage of the population that we wanted to pull out (ie: a population of 80 means 80 numbers to choose from, and a density of 50% means we want to choose 40 unique numbers at random from the population of 80).
I did 9 tests for each combination of 3 different populations (5, 250, and 12,500) and density values (10%, 50%, and 90%). Depending on how quickly or slowly the test was able to finish, I adjusted how many iterations of the test I performed (varied from just one pass to as many as 2,500 passes).
These were the results:
Solution 1
Solution 2
Solution 3
Comparison
Conclusion
As I suspected from the first solution, it really bogged down with large populations and high densities. It's still plenty quick when you don't have that much population or when you are only choosing like 2 numbers, but all solutions were pretty fast overall in those conditions. Even if it was the case that solution 1 could choose 25 random numbers from a population of 250 faster than solution 2 or 3 could, the real-time difference was pretty small.
However, it's useful to point out that if you want very few unique numbers from a really large population (ie: 2 unique numbers from a pool of 12,500), solution 1 is fastest, about 77% faster than solution 3, and both are orders of magnitude faster than solution 2. For my specific case, that's closer to what I will be doing almost all the time, so I will probably make a specific function that uses solution 1 for specifically choosing 2 unique numbers from a large pool of data.
Overall, solution 3 is pretty close to an all-around best algorithm. But with large data sets, consider each of these solutions based on what you plan on using them for.
I created 2 empty arrays "randomNumbers" and "blackList" then set up a for loop statement. In the loop statement I initialize the variable "number." I assign "number" to a random number, then I use a while statement and .contians to check if "number" is already in the array "randomNumbers" or if the "number" is in the array "blackList" if either array contains "number," "number is reassigned until neither array contains "number". Then I randomNumbers.append(number) to append "number" into "randomNumbers" then the loop starts over again.