14

I'm pretty new to Swift and programming logic in general so bear with me

How can you generate a random number between 0 and 9 in Swift without repeating the last generated number? As in the same number won't come up twice in a row.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
bmaliel
  • 353
  • 1
  • 5
  • 12
  • possible duplicate of [How does one generate a random number in Apple's Swift language?](http://stackoverflow.com/questions/24007129/how-does-one-generate-a-random-number-in-apples-swift-language) – Kampai Dec 18 '14 at 07:20
  • 4
    No, without repetition – bmaliel Dec 18 '14 at 07:21
  • You can do that part. By putting a simple condition. – Kampai Dec 18 '14 at 07:22
  • 1
    Yes I know that, but I don't know how to actually do that. That is why I am asking the question. As I said, I am very new to Swift – bmaliel Dec 18 '14 at 07:23
  • This can be done by only generating a single random number each time. See my answer. – vacawama Dec 18 '14 at 14:34
  • The answers on this page are profoundly bad! The simple and correct answer is very well-known. https://stackoverflow.com/a/62514324/294884 – Fattie Jun 22 '20 at 12:42

5 Answers5

17

my solution, i think its easy to understand

var nums = [0,1,2,3,4,5,6,7,8,9]

while nums.count > 0 {

    // random key from array
    let arrayKey = Int(arc4random_uniform(UInt32(nums.count)))

    // your random number
    let randNum = nums[arrayKey] 

    // make sure the number isnt repeated
    nums.swapAt(arrayKey, nums.count-1)
    nums.removeLast()
}
astruckm
  • 23
  • 6
hamobi
  • 7,940
  • 4
  • 35
  • 64
12

Store the previous generated number in a variable and compare the generated number to the previous number. If they match generate a new random number. Repeat the generation of new numbers until they don't match.

var previousNumber: UInt32? // used in randomNumber() 

func randomNumber() -> UInt32 {
    var randomNumber = arc4random_uniform(10)
    while previousNumber == randomNumber {
        randomNumber = arc4random_uniform(10)
    }
    previousNumber = randomNumber
    return randomNumber
}
Matthias Bauch
  • 89,811
  • 20
  • 225
  • 247
  • 3
    ... for which, in theory, any call to `randomNumber()` is not guaranteed to terminate for a given non-infinite amount of allowed iterations over the `while` loop (even if it, in practice, swiftly will :) ). For a deterministic solution (w.r.t. to program flow in the call to `randomNumber()`), [see vacawama:s clever answer below](http://stackoverflow.com/a/27548748/4573247). – dfrib Jan 29 '17 at 18:40
  • this answer is **totally wrong** and will crash. it's an indeterminate algorithm - a very basic computer science mistake. – Fattie Jun 22 '20 at 12:36
  • @Fattie: Can you elaborate? Why is it wrong? Under what conditions will this crash? – Martin R Jun 22 '20 at 12:45
  • my bad, by crash I meant enter an infinite loop – Fattie Jun 22 '20 at 12:47
  • `arc4random()` and also the new `Int.random(in:)` are well-tested pseudo-random number generators, they will not return the same number infinitely often. In the case of 10 numbers the probability of k repetitions is 1/10^k. – Martin R Jun 22 '20 at 12:50
  • well yes but you can't program based on "should be ok" or "it's very unlikely anyone will ever try to use this with an array of length 1" or "we know that certain software packages are tested" or "this will never be used at scale where performance matters" etc!! . no software can/should contain indeterminate loops. you just can't put a "naked while" in real software! IF you TRULY have to put a naked while in a routine, you absolutely must add a safety limit (count to 100 or something and abandon if you reach 100). all of this is incredibly moot, there is such a simple well-known solution – Fattie Jun 22 '20 at 13:11
  • Even the Swift standard library uses such loops in their implementation of the Random functions: https://github.com/apple/swift/blob/master/stdlib/public/core/Random.swift. – Martin R Jun 22 '20 at 19:04
9

Update for Swift 5

Here is a nice trick to choose equally from the numbers that were not just previously chosen.

You have 10 numbers, but you only want to select from 9 numbers (0 through 9, but excluding the previous number). If you reduce your range by 1, you can select from 9 random numbers and then just replace a repeated number with the previous top number of the range. In this way, you only have to generate a single random number each time and you get uniformity.

This can be implemented as Int.random(in:excluding:) where you pass the value you want to exclude.

extension Int {
    static func random(in range: ClosedRange<Int>, excluding x: Int) -> Int {
        if range.contains(x) {
            let r = Int.random(in: Range(uncheckedBounds: (range.lowerBound, range.upperBound)))
            return r == x ? range.upperBound : r
        } else {
            return Int.random(in: range)
        }
    }
}

Example:

// Generate 30 numbers in the range 1...3 without repeating the
// previous number  
var r = Int.random(in: 1...3)
for _ in 1...30 {
    r = Int.random(in: 1...3, excluding: r)
    print(r, terminator: " ")
}
print()

1 3 2 1 2 1 3 2 1 3 1 3 2 3 1 2 3 2 1 3 2 1 3 1 2 3 2 1 2 1 3 2 3 2 1 3 1 2 1 2


Previous Answer:

var previousNumber = arc4random_uniform(10)   // seed the previous number

func randomNumber() -> UInt32 {
    var randomNumber = arc4random_uniform(9)  // generate 0...8
    if randomNumber == previousNumber {
        randomNumber = 9
    }
    previousNumber = randomNumber
    return randomNumber
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 1
    @vacawama I see the trick here (uniformity for the 9 integers in the [0, 9] range where the previously chosen number has been excluded) now, thanks for explaining! – dfrib Jan 29 '17 at 18:17
  • 3
    Peculiarly, leaving aside the array shuffling methods, this is the only one-random-number-at-a-time-method that is guaranteed to "terminate" among the answers to this thread ("terminating" not really being an interesting term in the very termination-deterministic solution here, but w.r.t. the brute-force-looping approach used e.g. in the accepted answer). So I leave this additional comment pointing out that I believe this should be the accepted answer. – dfrib Jan 29 '17 at 18:29
  • 1
    It's worth noting that this method works great for choosing a second number after a first has been chosen, but doesn't work as well when you want to choose n random and unique numbers from a pool of k numbers. A collection of some sort is needed at that point. I'd recommend looking at my solution to a similar question here: https://stackoverflow.com/a/46029193/4273137 – Tim Fuqua Sep 04 '17 at 02:37
  • geez I'm sorry V but this answer is absolutely horrible. it's an incredibly well-known, straightforward problem with a trivial, well-known solution! https://stackoverflow.com/a/62514324/294884 – Fattie Jun 22 '20 at 12:41
  • What exactly is “absolutely horrible”? Can you be more specific? – Martin R Jun 22 '20 at 12:56
  • 1
    @Fattie, I checked your link and the algorithm that you provide isn’t very different from mine. Both uniformly choose from n-1 choices to avoid the repeat. Logically what my algorithm does is replace the excluded value with the one in the nth position and then choose from n-1 values. – vacawama Jun 22 '20 at 14:13
  • Vac regarding the upper solution. If I'm not mistaken, when there's a match you are choosing the "highest one". So it's very unrandom. Regarding the lower solution I believe it has the same problem. Honestly the solution is dead simple, it's one of the most basic tricks of modulo arithmetic. – Fattie Jun 22 '20 at 14:22
  • @Fattie, do you agree that if I first moved the highest value into the slot of the previous choice and then chose from those n-1 values that it would be random? That is exacly the same as sayiing, oh I chose the same value, so use the highest instead. The chance of picking the same value is 1/(n-1), so getting the highest value has the same chance as every other value. – vacawama Jun 22 '20 at 14:26
  • 1
    @Fattie, my answer is just as good as yours in terms of picking a random value without repeat. If you want to argue that doing modular arithmetic is faster than an "if", I'll give you that. But that hardly makes my answer horrible. – vacawama Jun 22 '20 at 14:32
5

The easiest way to do this is with repeat/while:

let current = ...
var next: Int

repeat {
    next = Int(arc4random_uniform(9))
} while current == next

// Use `next`
Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
0

Put all the values you want into an array, generate a random number using arc4random_uniform(SIZEOFARRAY) and pull the index of the random value from the array, and then repeat this process until the array is empty.

Alex J
  • 1,029
  • 6
  • 8