6

I need to start the same random number list over every execution of my app. srand/rand do not exist anymore. What should I do then?

private extension Array {
    private func randomValues(_ seed: UInt32, num: Int) -> [Element] {
        srand (seed)

        var indices = [Int]()
        indices.reserveCapacity(num)
        let range = 0..<self.count
        for _ in 0..<num {
            var random = 0
            repeat {
                random = randomNumberInRange(range)
            } while indices.contains(random)
            indices.append(random)
        }

        return indices.map { self[$0] }
    }
Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95
  • 1
    use arc4random() instead of srand(seed) – LC 웃 Jun 17 '16 at 03:48
  • 4
    `arc4random()` can't be seeded. You do get better quality random-numbers but if your requirement asks for the same set of random numbers every time, it's not suitable. – Code Different Jun 17 '16 at 04:00
  • Look like you have to move your code to C/Obj-C. Apple is either not porting it in time for Beta 1 or very heavy-handed in forcing you to use the "good" random number generators – Code Different Jun 17 '16 at 04:01
  • Yup, but what do you mean by the good? Higher quality generation? But anyway, sometime seeding is a must. I would fall from the tree if apple doesn't provide seeded generation. – Stéphane de Luca Jun 17 '16 at 04:05
  • Less predictable, longer cycle before the generator repeats itself. See my answer for a silly C wrapper – Code Different Jun 17 '16 at 04:15
  • [This blog post by Matt Gallagher](http://www.cocoawithlove.com/blog/2016/05/19/random-numbers.html) may be of interest to you. – pjs Jun 17 '16 at 14:42

5 Answers5

9

You can use srand48(seed) and drand48() in Swift3.

Lycoris
  • 106
  • 1
  • 1
5

Unless you're developing with Swift for non-Apple platforms, you can get a much better randomization API in GameplayKit: several algorithms (trade randomness vs speed), seedable, distribution control, etc.

rickster
  • 124,678
  • 26
  • 272
  • 326
2

I can't find a way to use seeded random in Swift 3 Beta 1. Had to write a silly wrapper function in C:

// ----------------------------------------------
// my_random.h
// ----------------------------------------------
#ifndef my_random_h
#define my_random_h

#include <stdio.h>

#endif /* my_random_h */

long next_random();


// ----------------------------------------------
// my_random.c
// ----------------------------------------------
#include <stdlib.h>
#include "my_random.h"

long next_random() {
    return random();
}

You can use the bridging header to import it into Swift. Then you can call it in Swift like this:

srandom(42)
for _ in 0..<10 {
    let x = next_random()
    print(x)
}

random is better than rand. Read the man pages for discussion on these 2 functions.


Edit:

A workaround, as @riskter suggested, is to use GameKit:

import GameKit

let seed = Data(bytes: [42]) // Use any array of [UInt8]
let source = GKARC4RandomSource(seed: seed)

for _ in 0..<10 {
    let x = source.nextInt()
    print(x)
}
Code Different
  • 90,614
  • 16
  • 144
  • 163
0

For a simple repeatable random list try using a Linear Congruential Generator:

import Foundation

class LinearCongruntialGenerator
{

    var state = 0 //seed of 0 by default
    let a, c, m, shift: Int

    //we will use microsoft random by default
    init() {
        self.a = 214013
        self.c = 2531011
        self.m = Int(pow(2.0, 31.0)) //2^31 or 2147483648
        self.shift = 16
    }

    init(a: Int, c: Int, m: Int, shift: Int) {
        self.a = a
        self.c = c
        self.m = m //2^31 or 2147483648
        self.shift = shift
    }

    func seed(seed: Int) -> Void {
        state = seed;
    }

    func random() -> Int {
        state = (a * state + c) % m
        return state >> shift
    }
}

let microsoftLinearCongruntialGenerator = LinearCongruntialGenerator()

print("Microsft Rand:")

for i in 0...10
{
    print(microsoftLinearCongruntialGenerator.random())
}

More info here: https://rosettacode.org/wiki/Linear_congruential_generator

Greg Robertson
  • 2,317
  • 1
  • 16
  • 30
  • I like it! Only instead of Int(pow(2.0, 31.0)), which is slow and inaccurate, since works with double values, I prefer 1<<31. – cyanide Dec 14 '16 at 08:33
0

I just happened to put this together for Swift 4. I am aware Swift 4.2 has new random extensions that are different from this, but like the OP, I needed them to be seedable during testing. Maybe someone will find it helpful. If you don't seed it, it will use arc4random, otherwise it will use drand48. It avoids mod bias both ways.

import Foundation 

class Random {

    static var number = unseededGenerator // the current generator

    /**
     * returns a random Int 0..<n
     **/
    func get(anIntLessThan n: Int) -> Int {
        return generatingFunction(n)
    }

    class func set(seed: Int) {
        number = seedableGenerator
        srand48(seed)
    }

    // Don't normally need to call the rest

    typealias GeneratingFunction = (Int) -> Int

    static let unseededGenerator = Random(){
        Int(arc4random_uniform(UInt32($0)))
    }
    static let seedableGenerator = Random(){
        Int(drand48() * Double($0))
    }

    init(_ gf: @escaping GeneratingFunction) {
        self.generatingFunction = gf
    }

    private let generatingFunction: GeneratingFunction
}

func randomTest() {
    Random.set(seed: 65) // comment this line out for unseeded
    for _ in 0..<10 {
        print(
            Random.number.get(anIntLessThan: 2),
            terminator: " "
        )
    }
}


// Run

randomTest()
adazacom
  • 443
  • 3
  • 9