-1

How do you make it so that a random generated number in an arc4random() set is never repeated. For example, in arc4random(100) in 1 is chosen, it will never be brought up again.

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var card: UIImageView!

    @IBOutlet weak var nextButton: UIButton!


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }




    @IBAction func nextCardTapped(sender: UIButton) {



        //Edit number of cards here for the random card to show up
        var randomCard = arc4random_uniform(100)
        // Edit number of cards here for the random card to show up


        var randomCardString:String = String(format: "card%i", randomCard)


        self.card.image = UIImage(named: randomCardString)
DC39
  • 1
  • 2
  • 2
    The title doesn't make sense. If it can never be repeated, it's not random. – Martin James Dec 23 '14 at 01:35
  • 4
    It sounds like you DON'T want to have a random number. But you want to shuffle a set of numbers. – Aron Dec 23 '14 at 01:41
  • 1
    Basically generate n random numbers ( or n numbers and shuffle them) store them in some sort of collection, use them up. – Tony Hopkinson Dec 23 '14 at 01:42
  • Typically you would create an array with the random objects and then use random numbers to shuffle them into a random order. After that you can just iterate through the array to get a non-repeating random object. – Dijkgraaf Dec 23 '14 at 01:44

4 Answers4

0

Are you going to be generating a few of these numbers, or a lot of them?

If you are only generating a few, then it might be worthwhile to keep a list of the ones you have already received and try again whenever you get one of them again. Something like...

NSMutableArray *uniqueNumbers = [[[NSMutableArray alloc] init] autorelease];

...

randomCard = arc4random_uniform(100);
while ([uniqueNumbers containsObject:[NSNumber numberWithInt:randomCard]]) {
    // try again
    randomCard = arc4random_uniform(100);
}
[uniqueNumbers addObject:[NSNumber numberWithInt:r]];

If you intend to generate many random numbers this way, it will get pretty inefficient. For instance, on the 99th number you will be looping repeatedly until by random chance you finally get one of the 2 numbers that aren't already in your list. If you want to generate a lot of numbers it might be more efficient to create a collection (array, stack, etc) with all of the numbers in it (1-100) and shuffle it. Then you can just pop them off the top one at a time.

NSMutableArray *uniqueNumbers = [[[NSMutableArray alloc] init] autorelease];
// build list
for(int i = 1; i <= 100; i = i + 1)
{
    [uniqueNumbers addObject:[NSNumber numberWithInt:i]];
}
// shuffle list
for(int i = 0; i < 100; i = i + 1)
{
    int n = arc4random() % 100;
    [uniqueNumbers exchangeObjectAtIndex:i withObjectAtIndex:n];
}
Matthew Pape
  • 1,681
  • 1
  • 22
  • 25
  • I am generating many numbers to call upon card images. Right now there is 300. – DC39 Dec 23 '14 at 01:51
  • The `% 99` is not correct. Also, `<= 100` is incorrect in second `for` loop. Better to use Fisher-Yates as referenced in duplicate question's accepted answer. – Rob Mar 22 '15 at 13:29
  • You are right. I have updated the code here in the hopes that it doesn't throw anyone off in the future. – Matthew Pape Mar 23 '15 at 17:41
  • Excellent edit! I might still suggest, though, that you consider using [Fisher Yates](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle) to eliminate the subtle bias the above algorithm introduces (as well as use `arc4random_uniform` to correct for the modulo bias of `%`). – Rob Mar 24 '15 at 13:53
0

What you are doing is not generating random numbers but displaying a set of numbers in a random order.

There is a very easy algorithm (and quick too) called the Fisher-Yates shuffle. It randomly shuffles an array. The only downside is that it means that any number in the array cannot be in the place it started out. (This may or may not be a downside in your case).

Anyway, you should create your array of numbers. 1-300 and then shuffle them.

Then when you iterate the array you will get the full set of 300 numbers without any repeats and in a random order each time you do it.

Here's the code I used. (Bare in mind it's 3am and I'm tired but I had to do it).

import UIKit

var numbers: [Int] = []

for number in 1...52 {
    numbers.append(number)
}

for i in 0..<(numbers.count - 1) {
    let j = Int(arc4random_uniform(UInt32(numbers.count - i))) + i
    swap(&numbers[i], &numbers[j])
}

println(numbers)

There may be a neater way to write it.

Edited the function as mine wasn't working. Adapted from this answer.

Community
  • 1
  • 1
Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • how would I randomize them with an array? – DC39 Dec 23 '14 at 02:01
  • The fisher Yates shuffle. I'm on my iPad at the moment so can't really do code properly. If you google it you will find how to do it. It takes about 6 or 7 lines of code. Once you shuffled the array just iterate it from the beginning. The contents are randomised not the iteration. – Fogmeister Dec 23 '14 at 02:03
  • @DC39 here you go http://en.m.wikipedia.org/wiki/Fisher–Yates_shuffle – Fogmeister Dec 23 '14 at 02:03
  • 1
    You can simplify population of `numbers`, e.g. `var numbers = [Int](1...52)`, but good answer! – Rob Mar 22 '15 at 13:19
0

Add the following category on NSMutableArray (this is the modified Fisher-Yates shuffle):

- (void) shuffle
{
    for (NSInteger i = [self count] - 1; i > 0; --i)
    {
        [self exchangeObjectAtIndex:arc4random() % (i+1) withObjectAtIndex: i];
    }
}

Generate the range of numbers sequentially (e.g., 1-300) and place them in an NSMutableArray (as NSNumber). Then, use the shuffle method above.

Then, pull the results sequentially from the NSMutableArray (indexes 0-299). You'll be drawing numbers randomly without replacement, which I think is what you're after.

RegularExpression
  • 3,531
  • 2
  • 25
  • 36
-3

Xcode 8 • Swift 3

extension Array {
    var shuffled: Array {
        var elements = self
        return elements.shuffle()
    }
    @discardableResult
    mutating func shuffle() -> Array {
        indices.dropLast().forEach { a in
            guard case let b = Int(arc4random_uniform(UInt32(count - a))) + a, b != a else { return }
            swap(&self[a], &self[b])
        }
        return self
    }
}

usage

let numbers = Array(1...100)

let shuffled = numbers.shuffled   // [5, 60, 15, 13, 94, 70, 17, 80, 95, 11, 76, 55, 96, 45, 1, 4, 23, 97, 48, 21, 47, 38, 51, 64, 59, 44, 19, 79, 98, 61, 35, 67, 49, 71, 83, 82, 54, 69, 36, 100, 73, 99, 93, 6, 62, 91, 24, 32, 10, 31, 41, 84, 46, 72, 12, 87, 20, 65, 43, 22, 85, 68, 86, 89, 88, 58, 33, 18, 30, 63, 52, 90, 57, 14, 16, 81, 2, 29, 37, 8, 50, 78, 9, 66, 3, 7, 77, 53, 39, 56, 28, 42, 25, 27, 34, 92, 26, 74, 75, 40]

let get10randomNumbers = numbers.shuffled.prefix(10)   // [71, 23, 33, 48, 73, 81, 75, 18, 19, 67]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • I am pretty new to coding. What does the while dealtNumbers.count <100 mean? – DC39 Dec 23 '14 at 01:52
  • so if i had say, 300 numbers it would be while dealtNumbers.count <300? – DC39 Dec 23 '14 at 01:57
  • 1
    @DC39 this is an extremely inefficient method. After you have done 298 numbers you are down to the last two. You might have 3 and 195 left not being used yet. So now you are stuck in a loop generating any of 300 random numbers until you happen to hit either 3 or 195. The more number you have the exponentially slower and slower this will become. My method does not have this disadvantage and works in O(N) time. Which is optimal for this problem. – Fogmeister Dec 23 '14 at 02:09
  • 1
    Even with 52 you should be using another method. With your answer you will need on average 1378 iterations to get all 52 numbers. With mine you need 52. For 300 you need on average 45,000 iterations, with mine you need 300. I'm not down voting because it won't work. I'm down voting because it is incredibly inefficient and there is a much more efficient way to do it. – Fogmeister Dec 23 '14 at 02:23
  • My answer is equally random. Yours just takes a lot longer to generate all numbers. – Fogmeister Dec 23 '14 at 02:24
  • My numbers may be off. It's 2:30 in the morning and I'm on my iPad. However, 1,350 is over 4 times 300. Hence, you have just proven that your method is over four times slower than my method. Thanks :-) – Fogmeister Dec 23 '14 at 02:34
  • @LeonardoSavioDabus Here you go, playing around a bit with XCTest... https://www.dropbox.com/s/yhyen67m2g55wfv/Screenshot%202014-12-23%2003.07.24.png?dl=0 For 1000 numbers your method takes 1.041 seconds. My method takes 0.020 seconds. That makes mine 520 times quicker for 1000 numbers generated in a random order. Like I said, your may way work. But it is ridiculously slow. – Fogmeister Dec 23 '14 at 03:09
  • @LeonardoSavioDabus just testing for 10,000 numbers. Mine took 0.204 seconds. Yours is still going. I'll update when it finishes. – Fogmeister Dec 23 '14 at 03:13
  • 1
    "Your method", "My method", I don't think either can claim ownership. That's why I called it the "Fisher-Yates Shuffle" as (IIRC) that is the name of the people who first created such a shuffle. It has been adapted and modernised since then but it keeps their name. As for my use of "your" and "mine" I am merely referring to the answer that you posted vs the answer that I posted (which is fairly obvious really). As for "your" method on 10,000 numbers... it's still going. I'm just trying to point out the importance of getting the right algorithm and knowing the complexity of what you're doing. – Fogmeister Dec 23 '14 at 03:20
  • You also seem to have taken the updated answer from here... http://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift Going to keep this running overnight now :) I'm interested to see how long it takes. – Fogmeister Dec 23 '14 at 03:25
  • @LeonardoSavioDabus it just finished as I was about to go to bed. So, 10,000 numbers. Fisher-Yates shuffle: 0.204 seconds. Saving previously generated random numbers: 1343.883 seconds (22 minutes ish). That's 6587 times longer that it took to run. For a relatively small set of numbers. – Fogmeister Dec 23 '14 at 03:37
  • 1
    My point is that you shouldn't be promoting the use of such inefficient algorithms. Especially not on a site like StackOverlow where (as an accepted answer it will spread into the wide world) and even more so in your own code. The fact that I have just shown you how slow it is (even for 52 numbers it was 3 or 4 times slower) you should now be diving into your own code and changing it. My point was that above all you need to understand the number space you are working in and the complexity of what you are doing. You are deliberately using a very inefficient algorithm here and now it's accepted. – Fogmeister Dec 23 '14 at 03:49
  • You have no idea who will read this accepted answer and what they will be using it for. Saying that you only use it for 52 numbers is a moot point. – Fogmeister Dec 23 '14 at 03:51
  • Nothing, I only commented because you asked "who cares? What is your point?" (Which you then edited). Tbh, the correct thing to do would be to only show the fisher Yates shuffle and delete the original answer. – Fogmeister Dec 23 '14 at 03:52
  • Why mention it at all? – Fogmeister Dec 23 '14 at 03:55
  • @LeonardoSavioDabus I don't mention that because that's complete rubbish. I guess you down voted mine. Oh well, some people will never learn. I reversed it because I was trying to iterate it backwards. It is still random. It may have higher numbers at the beginning if you run it once. Then again, it may not. Lol. Are you really trying to defend "your" method? Look, keep using your really slow code. I really don't care. Just don't try to tell other people to use it. It is wrong, that's why I down voted it. – Fogmeister Dec 23 '14 at 04:47
  • @LeonardoSavioDabus making up stuff about other peoples code doesn't make yours any more correct. Lol! – Fogmeister Dec 23 '14 at 04:51
  • @LeonardoSavioDabus lol, yup, I thougt so. You're still trying to defend code that even you have shown is very inefficient. I've shown that even for small numbers it is thousands of times slower. You still didn't explain your claim about my code being incorrect though. Lol. – Fogmeister Dec 23 '14 at 04:52
  • I will. Would love you to remove the down vote though. The reason you have put it on there is not correct. Unlike my down vote which is correct (as I have shown). :-) – Fogmeister Dec 23 '14 at 04:55
  • @LeonardoSavioDabus it is random. Try running it more than once. If you actually understood the code you took from someone else's answer then you would see that they are doing the same thing. – Fogmeister Dec 23 '14 at 05:00
  • @LeonardoSavioDabus I'm seriously not sure if you're just trolling now. No one could see the evidence that both you and I have shown and still believe that your original code is ok. Secondly, the code that you have in your edited answer is pretty much the same (done a slightly different way) as my code. Lol, oh well. Time to move on I think. Some discussions can never be won. I'll leave you with your inefficient code. – Fogmeister Dec 23 '14 at 05:05