Articles, podcasts and news about Swift development, by John Sundell.

Using Swift’s built-in randomization APIs

Published on 04 Mar 2020
Discover page available: The Standard Library

Swift offers quite a few different APIs that enable us to add various randomness features to our code. For example, let’s say that we wanted to generate a random number between 0 and 99 — that can be done simply by calling the static random() method on different numeric types, such as Int and Double:

let randomInt = Int.random(in: 0..<100)
let randomDouble = Double.random(in: 0..<100)

Worth noting is that when generating random Double or Float values (or any other non-integer type), fractional values are also included. So while the above randomInt has 100 potential values, randomDouble can contain any valid Double from 0.0 to 99.99..., giving us an amount of potential values that’s several orders of magnitude larger.

Swift’s various collections also ship with built-in randomization APIs as well. For example, here’s how we could extract a random element, or completely shuffle a collection:

// Drawing a random card from a deck
let deckOfCards: [Card] = makeDeck()
let card = deckOfCards.randomElement()

// Shuffling a list of players to randomize who gets to go first
var players: [Player] = loadPlayers()
players.shuffle()

Note that randomElement returns an optional, since we might be calling it on an empty collection.

One thing to keep in mind when adding randomization to a piece of code is how doing so might impact our ability to test that code. When writing unit tests, we’d ideally like to control the entire environment that our code is executed in, as to not cause flakiness (when tests start to sporadically fail) — and randomness is kind of the exact opposite of that sort of control.

To work around that problem, we could use function-based dependency injection to inject the randomization functions that we’re using into the types that we call them from. That way we’ll later be able to override those functions with predictable stubs within our tests.

For example, here we’re using that form of dependency injection to inject a Randomizer function into an AudioPlayer:

class AudioPlayer {
    typealias Randomizer = (Range<Int>) -> Int

    private let playlist: Playlist
    private let randomizer: Randomizer

    init(playlist: Playlist,
         randomizer: @escaping Randomizer = Int.random) {
        self.playlist = playlist
        self.randomizer = randomizer
    }

    func playRandomSong() {
        let index = randomizer(0..<playlist.songs.count)
        playSong(atIndex: index)
    }
    
    ...
}