Non repeating random numbers in Objective-C
Asked Answered
C

6

7

I'm using

for (int i = 1, i<100, i++)
    int i = arc4random() % array count;

but I'm getting repeats every time. How can I fill out the chosen int value from the range, so that when the program loops I will not get any dupe?

Cent answered 24/10, 2009 at 10:41 Comment(4)
see #1608681Veneer
If you don't want duplicates then you are not looking for randomness en.wikipedia.org/wiki/Randomness , sounds like you are looking for a randomized shuffling algorithm en.wikipedia.org/wiki/Random_permutationScapegoat
Thanks Maggie after a quick read about shuffling i think thats the algorithm im looking for.Cent
See https://mcmap.net/q/127493/-unique-non-repeating-random-numbers-in-o-1Sweeny
O
16

It sounds like you want shuffling of a set rather than "true" randomness. Simply create an array where all the positions match the numbers and initialize a counter:

num[ 0] =  0
num[ 1] =  1
: :
num[99] = 99
numNums = 100

Then, whenever you want a random number, use the following method:

idx = rnd (numNums);       // return value 0 through numNums-1
val = num[idx];            // get then number at that position.
num[idx] = val[numNums-1]; // remove it from pool by overwriting with highest
numNums--;                 //   and removing the highest position from pool.
return val;                // give it back to caller.

This will return a random value from an ever-decreasing pool, guaranteeing no repeats. You will have to beware of the pool running down to zero size of course, and intelligently re-initialize the pool.

This is a more deterministic solution than keeping a list of used numbers and continuing to loop until you find one not in that list. The performance of that sort of algorithm will degrade as the pool gets smaller.

A C function using static values something like this should do the trick. Call it with

int i = myRandom (200);

to set the pool up (with any number zero or greater specifying the size) or

int i = myRandom (-1);

to get the next number from the pool (any negative number will suffice). If the function can't allocate enough memory, it will return -2. If there's no numbers left in the pool, it will return -1 (at which point you could re-initialize the pool if you wish). Here's the function with a unit testing main for you to try out:

#include <stdio.h>
#include <stdlib.h>

#define ERR_NO_NUM -1
#define ERR_NO_MEM -2

int myRandom (int size) {
    int i, n;
    static int numNums = 0;
    static int *numArr = NULL;

    // Initialize with a specific size.

    if (size >= 0) {
        if (numArr != NULL)
            free (numArr);
        if ((numArr = malloc (sizeof(int) * size)) == NULL)
            return ERR_NO_MEM;
        for (i = 0; i  < size; i++)
            numArr[i] = i;
        numNums = size;
    }

    // Error if no numbers left in pool.

    if (numNums == 0)
       return ERR_NO_NUM;

    // Get random number from pool and remove it (rnd in this
    //   case returns a number between 0 and numNums-1 inclusive).

    n = rand() % numNums;
    i = numArr[n];
    numArr[n] = numArr[numNums-1];
    numNums--;
    if (numNums == 0) {
        free (numArr);
        numArr = 0;
    }

    return i;
}

int main (void) {
    int i;

    srand (time (NULL));
    i = myRandom (20);
    while (i >= 0) {
        printf ("Number = %3d\n", i);
        i = myRandom (-1);
    }
    printf ("Final  = %3d\n", i);
    return 0;
}

And here's the output from one run:

Number =  19
Number =  10
Number =   2
Number =  15
Number =   0
Number =   6
Number =   1
Number =   3
Number =  17
Number =  14
Number =  12
Number =  18
Number =   4
Number =   9
Number =   7
Number =   8
Number =  16
Number =   5
Number =  11
Number =  13
Final  =  -1

Keep in mind that, because it uses statics, it's not safe for calling from two different places if they want to maintain their own separate pools. If that were the case, the statics would be replaced with a buffer (holding count and pool) that would "belong" to the caller (a double-pointer could be passed in for this purpose).

And, if you're looking for the "multiple pool" version, I include it here for completeness.

#include <stdio.h>
#include <stdlib.h>

#define ERR_NO_NUM -1
#define ERR_NO_MEM -2

int myRandom (int size, int *ppPool[]) {
    int i, n;

    // Initialize with a specific size.

    if (size >= 0) {
        if (*ppPool != NULL)
            free (*ppPool);
        if ((*ppPool = malloc (sizeof(int) * (size + 1))) == NULL)
            return ERR_NO_MEM;
        (*ppPool)[0] = size;
        for (i = 0; i  < size; i++) {
            (*ppPool)[i+1] = i;
        }
    }

    // Error if no numbers left in pool.

    if (*ppPool == NULL)
       return ERR_NO_NUM;

    // Get random number from pool and remove it (rnd in this
    //   case returns a number between 0 and numNums-1 inclusive).

    n = rand() % (*ppPool)[0];
    i = (*ppPool)[n+1];
    (*ppPool)[n+1] = (*ppPool)[(*ppPool)[0]];
    (*ppPool)[0]--;
    if ((*ppPool)[0] == 0) {
        free (*ppPool);
        *ppPool = NULL;
    }

    return i;
}

int main (void) {
    int i;
    int *pPool;

    srand (time (NULL));
    pPool = NULL;
    i = myRandom (20, &pPool);
    while (i >= 0) {
        printf ("Number = %3d\n", i);
        i = myRandom (-1, &pPool);
    }
    printf ("Final  = %3d\n", i);
    return 0;
}

As you can see from the modified main(), you need to first initialise an int pointer to NULL then pass its address to the myRandom() function. This allows each client (location in the code) to have their own pool which is automatically allocated and freed, although you could still share pools if you wish.

Oleic answered 24/10, 2009 at 11:53 Comment(7)
Actually i already thought of having a problem with mem handling specially when im going to randomize 200+ numbers. One question though , how can i implement the above code if im gonna use the number to call out an image. NSString *ImageName = [NSString stringWithFormat :@"%d.png,i]; thanks.Cent
@Drahc, 200 numbers is not a lot. I'll add a C function to give you a start.Oleic
lolz. And here i am worrying mem consumption. Thanks for your help im really young at programming.Cent
No problems. Hopefully I'll be retired before you can present any threat to my job :-)Oleic
wow that was fast. Thanks pax im gonna try this later. Thanks again.Cent
@Drahc, there was a couple of syntax errors in the code - I've fixed them now and provided a version that can handle mutiple pools.Oleic
thanks pax, but right now im strugling on how to impliment this one on objective c. basically all i just want is to get a random value of i then use it in my image array using stringWithFormat to randomly add images in the UIImageView then discard the selected i value then goes to the loop to get another random value of i. But Thanks anyway for your help and effort.Cent
S
1

You could use Format-Preserving Encryption to encrypt a counter. Your counter just goes from 0 upwards, and the encryption uses a key of your choice to turn it into a seemingly random value of whatever radix and width you want.

Block ciphers normally have a fixed block size of e.g. 64 or 128 bits. But Format-Preserving Encryption allows you to take a standard cipher like AES and make a smaller-width cipher, of whatever radix and width you want (e.g. radix 2, width 16), with an algorithm which is still cryptographically robust.

It is guaranteed to never have collisions (because cryptographic algorithms create a 1:1 mapping). It is also reversible (a 2-way mapping), so you can take the resulting number and get back to the counter value you started with.

AES-FFX is one proposed standard method to achieve this. I've experimented with some basic Python code which is based on the AES-FFX idea, although not fully conformant--see Python code here. It can e.g. encrypt a counter to a random-looking 7-digit decimal number, or a 16-bit number.

Sweeny answered 2/12, 2012 at 14:2 Comment(0)
G
0

You need to keep track of the numbers you have already used (for instance, in an array). Get a random number, and discard it if it has already been used.

Goldston answered 24/10, 2009 at 11:10 Comment(1)
Shuffling is a much more efficient algorithm and fits the requirements: c-faq.com/lib/shuffle.htmlVeneer
T
0

Without relying on external stochastic processes, like radioactive decay or user input, computers will always generate pseudorandom numbers - that is numbers which have many of the statistical properties of random numbers, but repeat in sequences.

This explains the suggestions to randomise the computer's output by shuffling.

Discarding previously used numbers may lengthen the sequence artificially, but at a cost to the statistics which give the impression of randomness.

Tarpan answered 24/10, 2009 at 11:38 Comment(0)
F
0

The best way to do this is create an array for numbers already used. After a random number has been created then add it to the array. Then when you go to create another random number, ensure that it is not in the array of used numbers.

Flintlock answered 24/10, 2009 at 11:40 Comment(0)
E
0

In addition to using secondary array to store already generated random numbers, invoking random no. seeding function before every call of random no. generation function might help to generate different seq. of random numbers in every run.

Ebarta answered 24/10, 2009 at 11:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.