Randomness in Swift: Comprehensive overview
Random numbers, random array elements, random color, fair randomness and more.
Published: March 14, 2021 Sponsored App StoreThe days of arc4random_uniform
are thankfully long gone. In this post, let's go over the randomness that Swift and frameworks offer us. We will go over generating random numbers, random strings and and much more. Examples include getting random element from an array, generating array of random numbers, getting X random elements from an array & more.
Basic randomness
Generating random numbers in Swift is quite straightforward. For example to generate random Int
, we can use code like this:
let random = Int.random(0...100)
The random
method is also available on Double
, Float
and even CGFloat
type. Which is pretty nice since we don't need to deal with conversions.
let randomDouble = Double.random(in: 0...5)
let randomFloat = Float.random(in: 0...1)
let randomCGFloat = CGFloat.random(in: 0...2)
Just be careful that the result with these floating point types can be something like 4.2438943794
.
You can also get random Bool
by calling Bool.random()
. This might be useful in example projects where you want to show different code paths for example. Or to "simulate" 50/50 chance.
Random characters
Here things get a bit complicated. There is nothing like Character.random
to get random character. However we can ask our trusty ASCII table and convert numbers to characters. And we already know how to get random numbers!
The characters "A" to "Z" have ASCII codes 65 and 90 respectively, so this is the range for random uppercase letters from the English alphabet.
Here's how we could get random letters:
let randomLetter = Character(UnicodeScalar(Int.random(in: 65...90))!)
I know there is !
but I think we can be pretty confident in these numbers, it is not like ASCII table is going to change soon.
If we truly wanted random ASCII character, then we can use the entire range:
let randomCharacter = Character(UnicodeScalar(Int.random(in: 33...126))!)
There are other ways of creating random characters, it really depends on your needs. But I think this is a nice start.
And now we can create our own random
method:
extension Character {
static func random() -> Character {
return Character(UnicodeScalar(Int.random(in: 33...126))!)
}
}
And updated code above:
let randomCharacter = Character.random()
However if you plan to use the extension route, I think it would be better to have more sound implementation. Especially if other developers are going to use this code. Perhaps using the extended ASCII table.
Random strings
Since String
is a sequence of characters, we can easily use the above to create random strings.
If we want really short solution, we first need an extension on an Array
:
extension Array {
init(repeatingExpression expression: @autoclosure (() -> Element), count: Int) {
var temp = [Element]()
for _ in 0..<count {
temp.append(expression())
}
self = temp
}
}
Because the standard repeating initializer would repeat the same value. With this, we can get unique values.
let randomString = String(Array(repeatingExpression: Character.random(), count: 10))
This won't be the most performant solution, but if you don't need to generate hundreds of strings, then I think it works pretty well.
As an alternative you could possibly generate all the characters in advance, and then use something like Array.randomElement
to get the characters.
Array and randomness
We already touched a bit on randomness with arrays. With the randomElement
you can get single random element from an array. This is optional, because the array might be empty. So if the array is defined as a constant, this is great place for the !
.
let food = ["🍔","🍕","🌯","🍱","🍝"]
print("Lets eat: \(food.randomElement()!)")
I consider part of the randomness the methods shuffle
and shuffled
. The first will shuffle elements in place, the second one returns shuffled array.
let shuffledFood = food.shuffled()
Arrays of random numbers
What about of array of random numbers? With the above defined extension, that is a pretty easy thing to do.
let randomNumbers = Array(repeatingExpression: Int.random(in: 0...10), count: 100)
And done. We have 100 random integers with values between 0 and 10.
Similarly we can create random array of doubles, bools and other types.
let randomBools = Array(repeatingExpression: Bool.random(), count: 10)
Selecting random elements from array
We have seen how to get single random element from an array. But what if we want to get five random elements? Or 100? You can use randomElement
multiple times, but chances are, you don't want to introduce any duplicates.
One way is to leverage the prefix
function which gives us specified number of items from an array. If we shuffle the array first, then we basically get random subset of elements.
let randomNumbers = Array(repeatingExpression: Int.random(in: 0...10), count: 100)
let randomSample = randomNumbers.shuffled().prefix(10)
And now we have 10 random numbers from the 100 random numbers 🙂 That's nice and all.. But there is even better solution. Enter Swift Algorithms. This is a library available from Apple which will help us a lot.
After you add it to your project, start by importing it:
import Algorithms
And then we can get random sample:
let randomNumbers = Array(repeatingExpression: Int.random(in: 0...10), count: 100)
let randomSample = randomNumbers.randomSample(count: 10)
This is nice, but maybe not worth pulling in a library for. However if you need to preserve the order of the items after taking a sample, then this task becomes quite not so easy. Unless you make use of the Swift Algorithms that is:
let randomNumbers = Array(repeatingExpression: Int.random(in: 0...10), count: 100)
let randomSample = randomNumbers.randomStableSample(count: 10)
This sample will keep the ordering.
Random color
I think random color might be another useful use for randomness. Particularly if you are creating sample "data" and want to add a bit of visual flair. If you wanted to stick with the system colors, then you can create an array of these colors and use randomElement
which we used in the above example.
To create truly random color, we can make use of the UIColor
initializer that takes the HSB parameters. In this case we just need to generate random hue value. This is basically a wheel with all the colors present. For this reason we can use CGFloat.random(0...359)
, but generating floating point values between 0 and 1 also works for historic reasons.
So the code for UIColor.random
looks something like this:
extension UIColor {
static func random() -> UIColor {
return UIColor(hue: CGFloat.random(in: 0...359), saturation: 0.7, brightness: 1, alpha: 1)
}
}
I am using lower saturation so the resulting colors are bit more flat. If you use saturation 1, the colors will be very vivid. Alternatively you can also randomize the saturation, but I wouldn't recommend it.
Fair randomness
What do we mean by "fair" randomness? Let's use classic coin flip as an illustration. If you have a standard coin and take the time to flip it, say 1000 times, then you would expect to get roughly 500 heads and 500 tails.
Same for random numbers. If you generate 1000 numbers in the 0-10 range, then you would expect to get similar frequencies for all the possible numbers. Getting 500x "1" is not very useful randomness.
If randomness plays an important role in your app or game, then you should make sure the results are fair. There is great solution available in the form of the GameplayKit framework. As the name suggests, it is there for games but nothing can stop you from using it in your app.
GameplayKit randomness
We can start by looking at GKRandomDistribution
. This will try to produce all possible values in equal frequencies but you can still get same values in a row.
Start by importing the framework:
import GameplayKit
And then we can create the instance and get random number.
let distribution = GKRandomDistribution(lowestValue: 0, highestValue: 10)
let randomInt = distribution.nextInt()
Another useful class may be GKShuffledDistribution
. Here is what the docs say:
A generator for random numbers that are uniformly distributed across many samplings, but where short sequences of similar values are unlikely.
And then we can create the distribution and ask it to give us random number:
let distribution = GKShuffledDistribution(lowestValue: 0, highestValue: 10)
let randomInt = distribution.nextInt()
One of the other characteristics of this shuffled distribution is that it is designed to not repeat the same values in sequence. For some cases, this can make it maybe a too much fair.
Apple actually has pretty detailed guide available, check it out.
🎲 Also let's briefly look at the GKGaussianDistribution
This can be used to model stuff that follows Gaussian (Normal) distribution in real life. The simplest example may be a roll of two dice. It is actually pretty unlikely to get either 2 or 12 as the sum of the result. But 7 is pretty common because there are lots of ways two dice can produce this result.
Uses: Xcode 12 & Swift 5.3