views:

2385

answers:

9

How do I fill an integer array with unique values (no duplicates) in C?

int vektor[10];   

for (i = 0; i < 10; i++) {
    vektor[i] = rand() % 100 + 1;
}

//No uniqueness here
+1  A: 

One way would be to check if the array already contains the new random number, and if it does, make a new one and try again.

This opens up for the (random ;) ) possibility that you'd never get a number which is not in the array. Therefore you should count how many times you check if the number is already in the array, and if the count exceeds MAX_DUPLICATE_COUNT, throw an exception or so :) (EDIT, saw you're in C. Forget the exceptionpart :) Return an error code instead :P )

cwap
Well, I'd be quite surprized if I gave a function a well-defined task with a well-defined soluition, and the function would return with a "sorry, I just coudn't do it this time" error code :)
AndreyT
Haha, yeah, that would look awesome :)
cwap
+3  A: 

I think this will do it (I've not tried to build it, so syntax errors are left to fix as an exercise for the reader). There might be more elegant ways, but this is the brute force solution:

int vektor[10];    
int random;
int uniqueflag;
int i, j

for(i = 0; i < 10; i++) {
     /* Assume things are unique... we'll reset this flag if not. */
     uniqueflag = 1;
     do {
        random = rand() % 100+ 1;
        /* This loop checks for uniqueness */
        for (j = 0; j < i && uniqueflag == 1; j++) {
           if (vektor[j] == random) {
              uniqueflag = 0;
           }
        }
     } while (uniqueflag != 1);
     vektor[i] = random;
}
Chris J
Any algorithm that used the "try again" approach has very limited practical value. Actually, I'd say that the suffling approach is better, but the shuffling can be implemented better (see Knuth method in my reply).
AndreyT
+4  A: 

In your example (choose 10 unique random numbers between 1 and 100), you could create a list with the numbers 1 to 100, use the random number generator to shuffle the list, and then take the first 10 values from the list.

int list[100], vektor[10];
for (i = 0; i < 100; i++) {
    list[i] = i;
}
for (i = 0; i < 100; i++) {
    int j = i + rand() % (100 - i);
    int temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}
for (i = 0; i < 10; i++) {
    vektor[i] = list[i];
}

Based on cobbal's comment below, it is even better to just say:

for (i = 0; i < 10; i++) {
    int j = i + rand() % (100 - i);
    int temp = list[i];
    list[i] = list[j];
    list[j] = temp;

    vektor[i] = list[i];
}

Now it is O(N) to set up the list but O(M) to choose the random elements.

mobrule
That the better way.
Antoine Claval
Rather inefficient in general case, i.e. when the range length (say, N) is notably greater than the required array length (say, M). An efficient algorithm should be close to O(M). This one is O(N).
AndreyT
I agree -- see the accepted answer in eyalim's link.
mobrule
There is a small but not necessarily negligible bias in the random number mechanism used, but if you fix that, this is a good technique. Beware the upper bound in the middle loop; you can only swap list[99] with itself, which your code does, but it is a tad 'wasteful'.
Jonathan Leffler
it would be possible to run to i < 10, as once list[i] is assigned in the second loop, it doesn't change again.
cobbal
@mobrule: The accepted answer at the link is only good for situations when you need, say, 1000 numers out of 1000-long range. For the OP's problem that method will just generate more heat than light.
AndreyT
+6  A: 

See: http://stackoverflow.com/questions/196017/unique-random-numbers-in-o1

eyalm
This solution is only goog when you need 1000 numbers out of 1000, i.e. all numbers in certain random order (random permutation). For the general problem described in this thread (we need a few numbers from a long range) the method at the link is a rather wasteful and inefficient one. See the Floyd's algorithm in my reply for a much better method.
AndreyT
Then stop the algorithm after the 10th iteration. What's the problem? That we initialized 1000 ints instead of 1000 chars?
mobrule
No, the problem is that you initialized, say 10000 ints to generate, say, only 10 that you really need. What if I ask you to generate 20 random unique ints from the full 32-bit int range. Are you going to create an array of 4 billion ints? I use this example just as an illustration.
AndreyT
Again, shufflig *can* be used for the OP's problem, but it should be implemented wisely. See Knuth's method in my response for the proper shuffling-based implementation.
AndreyT
A: 

An quick solution is to create a mask array of all possible numbers initialized to zeros, and set an entry if that number is generated

int rand_array[100] = {0};
int vektor[10];   
int i=0, rnd;
while(i<10) {
    rnd = rand() % 100+ 1;
    if ( rand_array[rnd-1] == 0 ) {
        vektor[i++] = rnd;
        rand_array[rnd-1] = 1;
    }
}
Amro
A: 

A simple way would be to keep a record of the numbers you've already used. In your case, you appear to be interested in numbers between 1 and 100. So we have an array which specifies whether we have seen these numbers before. Then when we generate a random number we haven't seen before, we keep it and mark it as seen. If we generate one which we have seen before, we simply get another one.

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

#define MAX 100        /* Values will be in the range (1 .. MAX) */
static int seen[MAX];  /* These are automatically initialised to zero
                          by the compiler because they are static. */
static int vektor[10];

int main (void) {
  int i;

  srand(time(NULL));   /* Seed the random number generator. */

  for (i=0; i<10; i++) {
    int r;
    do {
      r = rand() / (RAND_MAX / MAX + 1);
    } while (seen[r]);
    seen[r] = 1;
    vektor[i] = r + 1;
  }

  for (i=0; i<10; i++)
    printf("%i\n", vektor[i]);

  return 0;
}

Note that I have also changed your use of rand() to give more random results. See the comp.lang.c FAQ list question 13.16 for details, if you're interested.

Tim
or directly see comp.lang.c FAQ list question 13.19 http://c-faq.com/lib/shuffle.html
Tring to look for an unused random number by iterative "trials an errors" is not a viable approach.
AndreyT
@AndreyT I'm intrigued why you think this isn't a viable approach. I'd agree that it doesn't scale very well, but for ten random numbers from 1 to 100 it's a good quick solution. Perhaps I'll write up a better approach.
Tim
Well, sorry, I can force myself to think of algorithms it terms of parameters of specific problem (like "10 out of 100"). Again, from algorithmic point of view generating a unique random number by "try, check, try again" approach is just... insulting to the very idea of algoritm, for the lack of better word.
AndreyT
You're right. I've put a different, better answer further down the page. Thanks for prompting my reconsideration. :-)
Tim
`seen` and `vektor` are initialized to 0 because they are file scope objects, not because they are `static`. The `static` makes them have "internal linkage" (makes them invisible to other translation units).
pmg
Remove the `static` and they'll get initialized to 0 all the same.
pmg
@pmg Thanks, I was pretty sure that what you said was correct, but ten minutes wrestling with the standard couldn't convince me that I wasn't mistaken. :-)
Tim
@Tim: turns out you're right. See 5.1.2 "Before program startup, the storage area that holds all objects with static storage duration shall first be cleared ..." and 6.2.4 $3. "An object whose identifier is declared with external or internal linkage, or with the storage-class specifier static has static storage duration."
pmg
@AndreyT: Completely agree. this is an "algorithm" that I would name "bogo-randomize" based on bogosort (http://en.wikipedia.org/wiki/Bogosort)
Brian Postow
@Brian OK, fair point. I'm really sorry. :-) Although I do like the name "bogo-randomize"!
Tim
This reminds me of a program I had to write in school that generated random strings from an input grammar. Most people assumed that rand() was random enough to use for jsut aobut anything. It turned out however that the low order bits were not random and in fact alternated between even and odd. One of the test cases was specifically designed to exploit this flaw and most people got into an infinite loop. The same thing could happen with this code, depending on the actual implementation of rand() and the range used. While it may look like this function is guaranteed to terminate, it is not
Dolphin
I've never seen an accepted answer with negative votes.
Ed Swangren
+21  A: 

There are several ways to solve your problem, each has its own advantages and disadvantages.

First I'd like to note that you already got quite a few of responses that do the following: they generate a random number, then check somehow whether it was already used in the array, and if it was already used, they just generate another number until they find an unused one. This is a naive and, truth to be said, seriously flawed approach. The problem is with the cyclic nature of the number generation ("if used, try again"). If the numeric range (say, [1..N]) is close to the length of the desired array (say, M), then towards the end the algorithm might spend a huge amount of time trying to find the next number. If the random number generator is even a little bit broken (say, never generates some number, or does it very rarely), then with N == M the algorithm is guaranteed to loop forever (or for a very long time). Generally this "trial and error" approach is a useless one, or flawed at best.

Another approach already presented here is generating a random permutation in an array of size N. The idea of random permutation is a promising one, but doing it on an array of size N (when M << N) is an atrocity.

The proper solutions to the problem can be found, for example, in Bentley's "Programming Pearls" (and some of them are taken from Knuth).


  • The Knuth algorithm. This is a very simple algorithm with a complexity of O(N) (i.e. the numeric range), meaning that it is most usable when M is close to N. However, this algorithm doesn't require any extra memory (as opposed to already offered variant with permutations) in addition to your vektor array (meaning that it takes O(M) memory, not O(N) as other permutation-based algorithm suggested here). The latter makes it a viable algorithm even for M << N cases.

The algorithm works as follows: iterate through all numbers from 1 to N and select the current number with probability rm / rn, where rm is how many numbers we still need to find, and rn is how many numbers we still need to iterate through. Here's a possible implementation for your case

#define M 10
#define N 100

int in, im;

im = 0;

for (in = 0; in < N && im < M; ++in) {
  int rn = N - in;
  int rm = M - im;
  if (rand() % rn < rm)    
    /* Take it */
    vektor[im++] = in + 1; /* +1 since your range begins from 1 */
}

assert(im == M);

After this cycle we get an array vektor filled with randomly chosen numbers in ascending order. The "ascending order" bit is what we don't need here. So, in order to "fix" that we just make a random permutation of elements of vektor and we are done. Note, that the this is a O(M) permutation with no extra memory. (I leave out the implementation of the permutation algorithm. Plenty of links was given here already.).

If you look carefully at those the permutation-based algorithms proposed here, that operate on an array of length N, you'll see that most of them are pretty much this very same Knuth algorithm, but re-formulated for M == N. In that case the above selection cycle will chose each and every number in [1..N] range with probabilty 1, effectively turning into initialization of an N-array with numbers 1 to N. Taking this into account, I think everybody will agree that running this algorithm for M == N and then truncating the result makes much less sense than just running this algorithm in its original form for the original value of M and getting the result right away, without any truncation.


  • The Floyd algorithm (see here). This approach has the complexity of about O(M) (depends on the search structure used), so it is better suitable when M << N. This approach keeps track of already generated random numbers, so it requires extra memory. However, the beauty of it is that it does not make any of those horrendous "trial and error" iterations, trying to find an unused random number. This algorithm is guaranteed to generate one unique random number after each single call to the random number generator.

Here's a possible implementation for it for your case. (There are different ways to keep track of already used numbers. I'll just use an array of flags, assuming that N is not exceedingly large)

#define M 10
#define N 100    

unsigned char is_used[N] = { 0 }; /* flags */
int in, im;

im = 0;

for (in = N - M; in < N && im < M; ++in) {
  int r = rand() % (in + 1);
  if (is_used[r])
    r = in; /* use 'in' instead of generated number */

  assert(!is_used[r]);
  vektor[im++] = r + 1; /* +1 since your range begins from 1 */
  is_used[r] = 1;
}

assert(im == M);

Why the above works is not immediately obvious. But it works. Exactly M numbers from [1..N] range will be picked with uniform distribution.

Note, that for large N you can use a search-based structure to store "used" numbers, thus getting a nice O(M log M) algorithm with O(M) memory requirement.

(There's one thing about this algorithm though: while the resultant array will not be ordered, a certain "influence" of the original 1..N ordering will still be present in the result. For example, it is obvious that number N, if selected, can only be the very last member of the resultant array. If this "hint" of ordering is not acceptable, the resultant vektor array can be random-shuffled, just like in the Khuth algorithm).


Note the very critical point observed in the design of these two algoritms: they never loop, trying to find a new unused random number. Any algorithm that makes "trial and error" iterations with random numbers is flawed from practical point of view. Also, the memory consumption of these algorithms is tied to M, not to N

To the OP I would recommend the Floyd's algorithm, since in his application M seems to be considerably less than N and that it doesn't (or may not) require an extra pass for permutation. However, for such small values of N the difference might be negligible.

AndreyT
I don't agree with your claim that "trial and error" is useless. The naive trial and error algorithm has a strong guarantee even when N==M (it completes in O(nlgn) time with high probability). For M<N/2, say, it completes in O(n) time with high probability.
Keith Randall
I can only say that this guarantee doesn't project well to practice. For N==M case the chances of running into an infinite loop with bad or low-quality 'rand()' are rather high (and the chances of getting a very long search times for the last elements even with good `rand()` are higher). I don't know how you can reasonably expect `O(n lg n)` in practice. In ideal world, maybe...
AndreyT
The O(n lg n) probably comes from an analysis similar to that of the (surprising) **coupon collector's problem**: http://en.wikipedia.org/wiki/Coupon_collector%27s_problem While a poor rand() might make things worse, as long as the rand() actually hits all values it should only be off by a constant: I do not know of any rand() implementation for which this would not be true.
ShreevatsaR
Or, an informal summary of the (short) solution to the coupon collector's problem: it is true that near the end of the list you may need to call rand() for O(n) times to find a new element, but this is only true for about O(log n) of them, so it all works out. Whether this O(n log n) is actually good enough is another matter: don't underestimate those logarithmic factors!
ShreevatsaR
I too disagree that trial and error is 'useless', as most reliable hill climbing algorithms employ it at some point. An interesting google would be the enigma m4 project, where steckers were hill climbed in a distributed network. Yet, +1, this is clearly the best answer to the question.
Tim Post
Haha, looks like I created Floyd algorithm from scratch on the interview today. Good info. +1
Grozz
+3  A: 

Simply generating random numbers and seeing whether they are OK is a poor way to solve this problem in general. This approach takes all the possible values, shuffles them and then takes the top ten. This is directly analogous to shuffling a deck of cards and dealing off the top.

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

#define randrange(N) rand() / (RAND_MAX/(N) + 1)

#define MAX 100        /* Values will be in the range (1 .. MAX) */
static int vektor[10];
int candidates[MAX];

int main (void) {
  int i;

  srand(time(NULL));   /* Seed the random number generator. */

  for (i=0; i<MAX; i++)
    candidates[i] = i;

  for (i = 0; i < MAX-1; i++) {
    int c = randrange(MAX-i);
    int t = candidates[i];
    candidates[i] = candidates[i+c];
    candidates[i+c] = t;
  }

  for (i=0; i<10; i++)
    vektor[i] = candidates[i] + 1;

  for (i=0; i<10; i++)
    printf("%i\n", vektor[i]);

  return 0;
}

For more information, see comp.lang.c FAQ list question 13.19 for shuffling and question 13.16 about generating random numbers.

Tim
A: 

Generate first and second digits separately. Shuffle them later if required. (syntax from memory)

int vektor[10];

int i = 0;
while(i < 10) {
    int j = rand() % 10;
    if (vektor[j] == 0) { vektor[j] = rand() % 10 + j * 10; i ++;}
}

However, the numbers will be nearly apart by n, 0 < n < 10.

Or else, you need to keep the numbers sorted (O(n log n)), so that newly generated can be quickly checked for presence (O(log n)).

Chandra