Gavin
Gavin

Reputation: 251

Calling different extent of randomness of arc4random in Swift?

This might be rather stupid question. I would like to know if different nuances/extent of randomness would be possible using arc4random_uniform in Swift. Here's an example:

let number = arc4random_uniform(10) + 1
print(number)

In this case, a number will be printed randomly from 1 to 10. But is there a way that I can repeat the random result, 2 to 3 times? The result would be something like this:

1, 1, 6, 6, 6, 3, 3, 8, 8, 9, 9, 9 ...

// 1) Randomly selected and 2) repeated 2 to 3 times randomly.

Perhaps I might use two arc4random_uniform functions together, but cannot express them properly. Would be much appreciated if you could give me some suggestions. <3

Upvotes: 0

Views: 71

Answers (3)

David Berry
David Berry

Reputation: 41226

I'd suggest a custom Sequence:

class RepeatingRandomSequence : Sequence {
    let rangeLow, rangeSpan : UInt32
    let repeatLow, repeatSpan : UInt32

    init(range:Range<UInt32>, count:Range<UInt32>) {
        rangeLow = range.lowerBound
        rangeSpan = range.upperBound - range.lowerBound + 1
        repeatLow = count.lowerBound
        repeatSpan = count.upperBound - count.lowerBound + 1
    }

    func makeIterator() -> AnyIterator<UInt32> {
        var count : UInt32 = 0
        var value : UInt32 = 0

        return AnyIterator {
            if(count <= 0) {
                count = arc4random_uniform(self.repeatSpan) + self.repeatLow
                value = arc4random_uniform(self.rangeSpan) + self.rangeLow
            }

            defer { count = count - 1 }

            return value
        }
    }
}

let sequence = RepeatingRandomSequence(range: 0..<10, count: 2..<3)
let randoms = sequence.makeIterator()

Note that the iterator, randoms now generates an endless sequence of random numbers using randoms.next() Since the sequence is endless, many things aren't particularly useful, like sort, map, etc. You could however use it like:

for value in random {
    print(value)
    if(value == 9) {  // or any other termination condition
        break
    }
}

Or more conventionally, as:

(0..<10).forEach { _ in
    print(String(describing: random.next()))
}

Upvotes: 1

vacawama
vacawama

Reputation: 154563

In order to do this, you will need to generate two values: your random value and a repeatCount. Also, you'll need to remember both of those values so that you can repeat the value. You can do this with a custom class:

class RandomWithRepeats {
    var range: ClosedRange<Int>
    var repeatRange: ClosedRange<Int>
    var repeatCount = 0
    var value = 0

    init(range: ClosedRange<Int>, repeatRange: ClosedRange<Int>) {
        self.range = range
        self.repeatRange = repeatRange
    }

    // generate a random number in a range
    // Just use Int.random(in:) with Swift 4.2 and later
    func random(in range: ClosedRange<Int>) -> Int {
        return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound + 1))) + range.lowerBound
    }

    func nextValue() -> Int {
        // if repeatCount is 0, its time to generate a new value and
        // a new repeatCount
        if repeatCount == 0 {
            // For Swift 4.2, just use Int.random(in:) instead
            value = self.random(in: range)
            repeatCount = self.random(in: repeatRange)
        }

        repeatCount -= 1
        return value
    }
}

Example:

let rand = RandomWithRepeats(range: 1...10, repeatRange: 2...3)

// generate 20 random repeated numbers    
for _ in 1...20
{
    print(rand.nextValue(), terminator: " ")
}
6 6 6 8 8 8 10 10 10 2 2 9 9 5 5 8 8 8 5 5 

Upvotes: 2

XmasRights
XmasRights

Reputation: 1509

With regards to the nuances of random number generators: have a look at GKRandomSource.

What you're doing here is not really making something less random, or modifying the parameters in the random number generator. You're simply applying an operation (with one random parameter) to a collection of random integers.

extension Collection {

    func duplicateItemsRandomly(range: CountableClosedRange<Int>) -> [Element] {

        return self.reduce(into: [Element](), { (acc, element) in

            let distance = UInt32(range.upperBound - range.lowerBound + 1)
            let count = Int(arc4random_uniform(distance) + UInt32(range.lowerBound))
            let result = Array.init(repeating: element, count: count)
            acc.append(contentsOf: result)
        })
    }
}

let sequence = [1, 6, 3, 8, 9]
sequence.duplicateItemsRandomly(range: 2...3) 
// [1, 1, 6, 6, 6, 3, 3, 3, 8, 8, 8, 9, 9, 9]

P.S: If you're writing this code in Swift 4.2, please use Int.random(in:).

Upvotes: 2

Related Questions