Learn everything what you'll ever need to generate random values in Swift using the latest methods and covering some old techniques.


How to generate random numbers using Swift?

Fortunately random number generation has been unified since Swift 4.2. This means that you don't have to mess around with imported C APIs anymore, you can simply generate random values by using native Swift methods on all platforms! ๐Ÿ˜

let randomBool = Bool.random() 
let randomInt = Int.random(in: 1...6) //dice roll 
let randomFloat = Float.random(in: 0...1)  
let randomDouble = Double.random(in: 1..<100)

As you can see generating a dice roll is now super easy, thanks to the cryptographically secure randomizer thatโ€™s built into the Swift language. The new random generator API also better at distributing the numbers. The old arc4random function had some issues, because the generated values were not uniformly distributed for example in between 1 and 6 due to the modulo bias side effect. ๐ŸŽฒ

Random Number Generator (RNG)

These examples above are implicitly using the default random number generator (SystemRandomNumberGenerator) provided by the Swift standard library. There is a second parameter for every method, so you can use a different RNG if you want. You can also implement your own RNG or extend the built-in generator, if you'd like to alter the behavior of distribution (or just give it some more "entropy"! ๐Ÿคช).

var rng = SystemRandomNumberGenerator()
let randomBool = Bool.random(using: &rng)
let randomInt = Int.random(in: 1...6, using: &rng) //dice roll
let randomFloat = Float.random(in: 0...1, using: &rng)
let randomDouble = Double.random(in: 1..<100, using: &rng)

Collections, random elements, shuffle

The new random API introduced some nice extensions for collection types. Picking a random element and mixing up the order of elements inside a collection is now ridiculously easy and performant (with custom RNG support as well). ๐Ÿ˜‰

let array = ["๐Ÿถ", "๐Ÿฑ", "๐Ÿฎ", "๐Ÿท", "๐Ÿ”", "๐Ÿต"]
let randomArrayElement = array.randomElement()
let shuffledArray = array.shuffled()

let dictionary = [
    "๐Ÿต": "๐ŸŒ",
    "๐Ÿฑ": "๐Ÿฅ›",
    "๐Ÿถ": "๐Ÿ–",
]
let randomDictionaryElement = dictionary.randomElement()
let shuffledDictionary = dictionary.shuffled()

let sequence = 1..<10
let randomSequenceElement = sequence.randomElement()
let shuffledSequence = sequence.shuffled()

let set = Set<String>(arrayLiteral: "๐Ÿถ", "๐Ÿฑ", "๐Ÿฎ", "๐Ÿท", "๐Ÿ”", "๐Ÿต")
let randomSetElement = set.randomElement()
let shuffledSet = set.shuffled()

Randomizing custom types

You can implement random functions on your custom types as well. There are two simple things that you should keep in mind in order to follow the Swift standard library pattern:

  • provide a static method that has a (inout) parameter for the custom RNG
  • make a random() method that uses the SystemRandomNumberGenerator
enum Animal: String, CaseIterable {
    case dog = "๐Ÿถ"
    case cat = "๐Ÿฑ"
    case cow = "๐Ÿฎ"
    case pig = "๐Ÿท"
    case chicken = "๐Ÿ”"
    case monkey = "๐Ÿต"
}

extension Animal {

    static func random<T: RandomNumberGenerator>(using generator: inout T) -> Animal {
        return self.allCases.randomElement(using: &generator)!
    }
    
    static func random() -> Animal {
        var rng = SystemRandomNumberGenerator()
        return Animal.random(using: &rng)
    }
}

let random: Animal = .random()
random.rawValue

Generating random values using GameplayKit

The GameplayKit provides lots of things to help you dealing with random number generation. Various random sources and distributions are available inside the framework, let's have a quick look at them.

Random sources in GameplayKit

GameplayKit has three random source algorithms implemented, the reason behind it is that random number generation is hard, but usually you're going to go with arc4 random source. You should note that Apple recommends resetting the first 769 values (just round it up to 1024 to make it look good) before you're using it for something important, otherwise it will generate sequences that can be guessed. ๐Ÿ”‘

GKARC4RandomSource - ok performance and randomness

GKLinearCongruentialRandomSource - fast, less random

GKMersenneTwisterRandomSource - good randomness, but slow

You can simply generate a random number from int min to int max by using the nextInt() method on any of the sources mentioned above or from 0 to upper bound by using the nextInt(upperBound:) method.

import GameplayKit

let arc4 = GKARC4RandomSource()
arc4.dropValues(1024) //drop first 1024 values first
arc4.nextInt(upperBound: 20)
let linearCongruential = GKLinearCongruentialRandomSource()
linearCongruential.nextInt(upperBound: 20)
let mersenneTwister = GKMersenneTwisterRandomSource()
mersenneTwister.nextInt(upperBound: 20)

Random distribution algorithms

GKRandomDistribution - A generator for random numbers that fall within a specific range and that exhibit a specific distribution over multiple samplings.

Basically we can say that this implementation is trying to provide randomly distributed values for us. It's the default value for shared random source. ๐Ÿคจ

GKGaussianDistribution - A generator for random numbers that follow a Gaussian distribution (also known as a normal distribution) across multiple samplings.

The gaussian distribution is a shaped random number generator, so it's more likely that the numbers near the middle are more frequent. In other words elements in the middle are going to occure significantly more, so if you are going to simulate dice rolling, 3 is going to more likely happen than 1 or 6. Feels like the real world, huh? ๐Ÿ˜…

GKShuffledDistribution - A generator for random numbers that are uniformly distributed across many samplings, but where short sequences of similar values are unlikely.

A fair random number generator or shuffled distribution is one that generates each of its possible values in equal amounts evenly distributed. If we keep the dice rolling example with 6 rolls, you might get 6, 2, 1, 3, 4, 5 but you would never get 6 6 6 1 2 6.

// 6 sided dice
let randomD6 = GKRandomDistribution.d6()
let shuffledD6 = GKShuffledDistribution.d6()
let gaussianD6 = GKGaussianDistribution.d6()
randomD6.nextInt()   // completely random
shuffledD6.nextInt() // see below... // eg. 1
gaussianD6.nextInt() // mostly 3, most likely 2, 4 less likely 1, 6

//goes through all the possible values again and again...
shuffledD6.nextInt() // eg. 3
shuffledD6.nextInt() // eg. 5
shuffledD6.nextInt() // eg. 2
shuffledD6.nextInt() // eg. 6
shuffledD6.nextInt() // eg. 4

// 20 sided dice
let randomD20 = GKRandomDistribution.d20()
let shuffledD20 = GKShuffledDistribution.d20()
let gaussianD20 = GKGaussianDistribution.d20()
randomD20.nextInt()
shuffledD20.nextInt()
gaussianD20.nextInt()

// using custom random source, by default it uses arc4
let mersenneTwister = GKMersenneTwisterRandomSource()
let mersoneTwisterRandomD6 = GKRandomDistribution(randomSource: mersenneTwister, lowestValue: 1, highestValue: 6)
mersoneTwisterRandomD6.nextInt()
mersoneTwisterRandomD6.nextInt(upperBound: 3) //limiting upper bound

How to shuffle arrays using GameplayKit?

You can use the arrayByShufflingObjects(in:) method to mix up elements inside an array. Also you can use a seed value in order to shuffle elements identically. It's going to be a random order, but it can be predicted. This comes handy if you need to sync two random arrays between multiple devices. ย ๐Ÿ“ฑ

let dice = [Int](1...6)

let random = GKRandomSource.sharedRandom()
let randomRolls = random.arrayByShufflingObjects(in: dice)

let mersenneTwister = GKMersenneTwisterRandomSource()
let mersenneTwisterRolls = mersenneTwister.arrayByShufflingObjects(in: dice)

let fixedSeed = GKMersenneTwisterRandomSource(seed: 1001)
let fixed1 = fixedSeed.arrayByShufflingObjects(in: dice) // always the same order

GameplayKit best practice to generate random values

There is also a shared random source that you can use to generate random numbers. This is ideal if you don't want to mess around with distributions or sources. This shared random object uses arc4 as a source and random distribution. ๐Ÿ˜‰

let sharedRandomSource = GKRandomSource.sharedRandom()
sharedRandomSource.nextBool() // true / false
sharedRandomSource.nextInt() //from int min - to int max
sharedRandomSource.nextInt(upperBound: 6) //dice roll
sharedRandomSource.nextUniform() //float between 0 - 1

Please note that none of these random number generation solutions provided by the GameplayKit framework are recommended for cryptography purposes!


Pre-Swift 4.2 random generation methods

arc4random

arc4random() % 6 + 1 // dice roll

This C function was very common to generate a dice roll, but it's also dangerous, because it can lead to a modulo bias (or pigenhole principle), that means some numbers are generated more frequently than others. Please don't use it. ๐Ÿ˜…

arc4random_uniform

This method will return a uniformly distributed random numbers. It was the best / recommended way of generating random numbers before Swift 4.2, because it avoids the modulo bias problem, if the upper bound is not a power of two.

func rndm(min: Int, max: Int) -> Int {
    if max < min {
        fatalError("The max value should be greater than the min value.")
    }
    if min == max {
        return min
    }
    return Int(arc4random_uniform(UInt32((max - min) + 1))) + min
}
rndm(min: 1, max: 6) //dice roll

drand48

The drand48 function returns a random floating point number between of 0 and 1. It was really useful for generating color values for random UIColor objects. One minor side note that it generates a pseudo-random number sequence, and you have to provide a seed value by using srand48 and usually a time parameter. ๐Ÿคทโ€โ™‚๏ธ

let red = CGFloat(drand48())
let green = CGFloat(drand48())
let blue = CGFloat(drand48())

Linux support, glibc and the rand method

I was using this snippet below in order to generate random numbers on both appleOS and linux platform. I know it's not perfect, but it did the job for me. ๐Ÿค

#!/usr/bin/env swift

#if os(iOS) || os(tvOS) || os(macOS) || os(watchOS)
    import Darwin
#endif
#if os(Linux)
    import Glibc
#endif

public func rndm(to max: Int, from min: Int = 0) -> Int {
    #if os(iOS) || os(tvOS) || os(macOS) || os(watchOS)
        let scale = Double(arc4random()) / Double(UInt32.max)
    #endif
    #if os(Linux)
        let scale = Double(rand()) / Double(RAND_MAX)
    #endif
    var value = max - min
    let maximum = value.addingReportingOverflow(1)
    if maximum.overflow {
        value = Int.max
    }
    else {
        value = maximum.partialValue
    }
    let partial = Int(Double(value) * scale)
    let result = partial.addingReportingOverflow(min)
    if result.overflow {
        return partial
    }
    return result.partialValue
}

rndm(to: 6)

Now that we have Swift 4.2 just around the corner I'd like to encourage everyone to adapt the new random number generation API methods. I'm really glad that Apple and the community tackled down this issue so well, the results are amazing! ๐Ÿ‘๐Ÿ‘๐Ÿ‘


External sources