1

I'm trying to make a get_random_4digit function that generates a 4 digit number that has non-repeating digits ranging from 1-9 while only using ints, if, while and functions, so no arrays etc.

This is the code I have but it is not really working as intended, could anyone point me in the right direction?

int get_random_4digit() {
    int d1 = rand() % 9 + 1;
    int d2 = rand() % 9 + 1;

    while (true) {
        if (d1 != d2) {
            int d3 = rand() % 9 + 1;
            if (d3 != d1 || d3 != d2) {
                int d4 = rand() % 9 + 1;
                if (d4 != d1 || d4 != d2 || d4 != d3) {
                    random_4digit = (d1 * 1000) + (d2 * 100) + (d3 * 10) + d4;
                    break;
                }
            }
        }
    }
    printf("Random 4digit = %d\n", random_4digit);
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 3
    There is a finite and limited number of such a numbers. I suggest simply generating all of them, then pick one randomly. This will be way faster than trial-and-error approach. – Eugene Sh. Nov 08 '21 at 18:52
  • 1
    Does this answer your question? [Unique (non-repeating) random numbers in O(1)?](https://stackoverflow.com/questions/196017/unique-non-repeating-random-numbers-in-o1) – Retired Ninja Nov 08 '21 at 18:52
  • 3
    Randomly select `d1`. Then `while (d2 == d1)` randomly select `d2`. Then `while (d3 == d2 || d3 == d1)` ... hopefully you get the picture. – user3386109 Nov 08 '21 at 18:53
  • 2
    Alternatively (a bit suboptimal though, I think), simply shuffle an array of 10 digits using Fisher-Yates algorithm, and then simply take first 4 elements. – Eugene Sh. Nov 08 '21 at 19:08
  • 1
    I would suggest to make a list of 9 `int`s, 1-9, and then pick 4. If you really want it, you can use a `uint64_t` and use 4 bit per digits, if you really want to avoid arrays. – 12431234123412341234123 Nov 08 '21 at 19:17
  • @12431234123412341234123 Both - "pick 4" and "arrange 4 in random order" are not trivial tasks if one wants to maintain uniform distribution. – Eugene Sh. Nov 08 '21 at 19:19
  • Are this decimal numbers or numbers base 9? Since you only use 9 digits. – 12431234123412341234123 Nov 08 '21 at 19:21
  • I know this is probably a non-helpful comment, but whenever I see questions with these contrived restrictions, I always wonder: *what on earth is the point of disallowing arrays*? You can solve this problem clearly and concisely using arrays, or obscurely and complicatedly without arrays. The world is already drowning in obscure and complicated code. Why encourage students to write more of it? – Steve Summit Nov 09 '21 at 12:46
  • @SteveSummit the point of disallowing arrays is because im taking a beginners course in programming and we havent gotten to arrays yet, so we have to use the functions we have learned so far... – Milton Niklasson Nov 09 '21 at 12:50
  • @MiltonNiklasson That's fine, but it seems to me that if your instructor had any sense, they would avoid assigning problems that demand arrays for a good solution until after teaching arrays! – Steve Summit Nov 09 '21 at 12:55

7 Answers7

3

A KISS-approach could be this:

int getRandom4Digits() {
    uint16_t acc = 0;
    uint16_t used = 0;
    for (int i = 0; i < 4; i++) {
        int idx;
        do {
            idx = rand() % 9;  // Not equidistributed but never mind...
        } while (used & (1 << idx));
        acc = acc * 10 + (idx + 1);
        used |= (1 << idx);
    }

    return acc;
}

This looks terribly dumb at first. A quick analysis gives that this really isn't so bad, giving a number of calls to rand() to be about 4.9.

The expected number of inner loop steps [and corresponding calls to rand(), if we assume rand() % 9 to be i.i.d.] will be: 9/9 + 9/8 + 9/7 + 9/6 ~ 4.9107.

2

There are 9 possibilities for the first digit, 8 possibilities for the second digit, 7 possibilities for the third digit and 6 possibilities for the last digit. This works out to "9*8*7*6 = 3024 permutations".

Start by getting a random number from 0 to 3023. Let's call that P. To do this without causing a biased distribution use something like do { P = rand() & 0xFFF; } while(P >= 3024);.

Note: If you don't care about uniform distribution you could just do P = rand() % 3024;. In this case lower values of P will be more likely because RAND_MAX doesn't divide by 3024 nicely.

The first digit has 9 possibilities, so do d1 = P % 9 + 1; P = P / 9;.

The second digit has 8 possibilities, so do d2 = P % 8 + 1; P = P / 8;.

The third digit has 7 possibilities, so do d3 = P % 7 + 1; P = P / 7;.

For the last digit you can just do d4 = P + 1; because we know P can't be too high.

Next; convert "possibility" into a digit. For d1 you do nothing. For d2 you need to increase it if it's greater than or equal to d1, like if(d2 >= d1) d2++;. Do the same for d3 and d4 (comparing against all previous digits).

The final code will be something like:

int get_random_4digit() {
    int P, d1, d2, d3, d4;

    do {
        P = rand() & 0xFFF;
    } while(P >= 3024);

    d1 = P % 9 + 1; P = P / 9;
    d2 = P % 8 + 1; P = P / 8;
    d3 = P % 7 + 1; P = P / 7;
    d4 = P + 1;

    if(d2 >= d1) d2++;
    if(d3 >= d1) d3++;
    if(d3 >= d2) d3++;
    if(d4 >= d1) d4++;
    if(d4 >= d2) d4++;
    if(d4 >= d3) d4++;

    return d1*1000 + d2*100 + d3*10 + d4;
}
Brendan
  • 35,656
  • 2
  • 39
  • 66
  • Isn't it `{9,9,8,7}` possibilities, if you include 0 for all but the first? – Neil Nov 08 '21 at 19:41
  • 2
    @Neil OP's question says that only digits 1-9 are used. – Ian Abbott Nov 08 '21 at 19:42
  • 1
    @Neil: Yes, it would be. The question says "...and digits ranging from 1-9 " (and doesn't say "... and the first digit ranging from 1-9") though, so.. – Brendan Nov 08 '21 at 19:43
  • I missed that. This is the most elegant because it doesn't throw out nearly as many bits. The [c-faq](http://c-faq.com/lib/randrange.html) says that low-order bits in many implementations are not random, recommend div instead of mod. – Neil Nov 08 '21 at 20:00
  • 2
    @Neil: Eww - if the low-order bits aren't as random, the recommendation should be to send a bug report to the C library developers and switch to a better implementation. ;-) – Brendan Nov 08 '21 at 20:25
  • Technically, `rand() & 0xFFF` *could* be biased, i.e. if `(RAND_MAX & 0xFFF) != 0xFFF)`. Unlikely though. – Ian Abbott Nov 08 '21 at 21:29
  • Shouldn't `d4 = P;` be `d4 = P + 1;`? (You have that in the main text, but not in the code!) – Ian Abbott Nov 08 '21 at 21:59
  • @IanAbbott: yes - fixed now (thanks) – Brendan Nov 08 '21 at 23:21
1

You could start with an integer number, 0x123456789, and pick random nibbles from it (the 4 bits that makes up one of the digits in the hex value). When a nibble has been selected, remove it from the number and continue picking from those left.

This makes exactly 4 calls to rand() and has no if or other conditions (other than the loop condition).

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

int get_random_4digit() {
    uint64_t bits = 0x123456789; // nibbles

    int res = 0;

    // pick random nibbles
    for(unsigned last = 9 - 1; last > 9 - 1 - 4; --last) {
        unsigned lsh = last * 4;                  // shift last nibble
        unsigned sel = (rand() % (last + 1)) * 4; // shift for random nibble

        // multiply with 10 and add the selected nibble
        res = res * 10 + ((bits & (0xFULL << sel)) >> sel);

        // move the last unselected nibble right to where the selected
        // nibble was:
        bits = (bits & ~(0xFULL << sel)) |
               ((bits & (0xFULL << lsh)) >> (lsh - sel));
    }
    return res;
}

Demo


Another variant could be to use the same value, 0x123456789, and do a Fisher-Yates shuffle on the nibbles. When the shuffle is done, return the 4 lowest nibbles. This is more expensive since it randomizes the order of all 9 nibbles - but it makes it easy if you want to select an arbitrary amount of them afterwards.

Example:

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

uint16_t get_random_4digit() {
    uint64_t bits = 0x123456789; // nibbles

    // shuffle the nibbles
    for(unsigned idx = 9 - 1; idx > 0; --idx) {
        unsigned ish = idx * 4; // index shift
        // shift for random nibble to swap with `idx`
        unsigned swp = (rand() % (idx + 1)) * 4;

        // extract the selected nibbles
        uint64_t a = (bits & (0xFULL << ish)) >> ish;
        uint64_t b = (bits & (0xFULL << swp)) >> swp;

        // swap them
        bits &= ~((0xFULL << ish) | (0xFULL << swp));
        bits |= (a << swp) | (b << ish);
    }
    return bits & 0xFFFF; // return the 4 lowest nibbles
}

The bit manipulation can probably be optimized - but I wrote it like I thought it so it's probably better for readability to leave it as-is

You can then print the value as a hex value to get the output you want - or extract the 4 nibbles and convert it for decimal output.

int main() {
    srand(time(NULL));

    uint16_t res = get_random_4digit();

    // print directly as hex:
    printf("%X\n", res);

    // or extract the nibbles and multiply to get decimal result - same output:
    uint16_t a = (res >> 12) & 0xF;
    uint16_t b = (res >> 8) & 0xF;
    uint16_t c = (res >> 4) & 0xF;
    uint16_t d = (res >> 0) & 0xF;
    uint16_t dec = a * 1000 + b * 100 + c * 10 + d;
    printf("%d\n", dec);
}

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
0

You should keep generating digits until distinct one found:

int get_random_4digit() {
  int random_4digit = 0;

  /* We must have 4 digits number - at least 1234 */
  while (random_4digit < 1000) {
    int digit = rand() % 9 + 1;  

    /* check if generated digit is not in the result */
    for (int number = random_4digit; number > 0; number /= 10) 
      if (number % 10 == digit) {
        digit = 0; /* digit has been found, we'll try once more */

        break;
      }

    if (digit > 0) /* unique digit generated, we add it to result */
      random_4digit = random_4digit * 10 + digit;
  }

  return random_4digit; 
}

Please, fiddle youself

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
0

You could use a partial Fisher-Yates shuffle on an array of 9 digits, stopping after 4 digits:

// Return random integer from 0 to n-1
// (for n in range 1 to RAND_MAX+1u).
int get_random_int(unsigned int n) {
    unsigned int x = (RAND_MAX + 1u) / n;
    unsigned int limit = x * n;
    int s;
    do {
        s = rand();
    } while (s >= limit);
    return s / x;
}

// Return random 4-digit number from 1234 to 9876 with no
// duplicate digits and no 0 digit.
int get_random_4digit(void) {
    char possible[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int result = 0;
    int i;
    // Uses partial Fisher-Yates shuffle.
    for (i = 0; i < 4; i++) {
        // Get random position rand_pos from remaining possibilities i to 8
        // (positions before i contain previous selected digits).
        int rand_pos = i + get_random_int(9 - i);
        // Select digit from position rand_pos.
        char digit = possible[rand_pos];
        // Exchange digits at positions i and rand_pos.
        possible[rand_pos] = possible[i];
        possible[i] = digit; // not really needed
        // Put selected digit into result.
        result = result * 10 + digit;
    }
    return result;
}

EDIT: I forgot the requirement "while only using int's, if, while and functions, so no arrays etc.", so feel free to ignore this answer!

If normal C integer types are allowed including long long int, the get_random_4digit() function above can be replaced with the following to satisfy the requirement:

// Return random 4-digit number from 1234 to 9876 with no
// duplicate digits and no 0 digit.
int get_random_4digit(void) {
    long long int possible = 0x123456789; // 4 bits per digit
    int result = 0;
    int i;
    // Uses partial Fisher-Yates shuffle.
    i = 0;
    while (i < 4) {
        // Determine random position rand_pos in remaining possibilities 0 to 8-i.
        int rand_pos = get_random_int(9 - i);
        // Select digit from position rand_pos.
        int digit = (possible >> (4 * rand_pos)) & 0xF;
        // Replace digit at position rand_pos with digit at position 0.
        possible ^= ((possible ^ digit) & 0xF) << (4 * rand_pos);
        // Shift remaining possible digits down one position.
        possible >>= 4;
        // Put selected digit into result.
        result = result * 10 + digit;
        i++;
    }
    return result;
}
Ian Abbott
  • 15,083
  • 19
  • 33
0

One way to do this is to create an array with all 9 digits, pick a random one and remove it from the list. Something like this:

uint_fast8_t digits[]={1,2,3,4,5,6,7,8,9}; //only 1-9 are allowed, 0 is not allowed
uint_fast8_t left=4; //How many digits are left to create
unsigned result=0; //Store the 4-digit number here
while(left--)
{
  uint_fast8_t digit=getRand(9-4+left); //pick a random index
  result=result*10+digits[digit];
  //Move all digits above the selcted one 1 index down.
  //This removes the picked digit from the array.
  while(digit<8)
  {
    digits[digit]=digits[digit+1];
    digit++;
  }
}

You said you need a solution without arrays. Luckily, we can store up to 16 4 bit numbers in a single uint64_t. Here is an example that uses a uint64_t to store the digit list so that no array is needed.

#include <stdint.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

unsigned getRand(unsigned max)
  {
    return rand()%(max+1);
  }

//Creates a uint64_t that is used as an array.
//Use no more than 16 values and don't use values >0x0F
//The last argument will have index 0
uint64_t fakeArrayCreate(uint_fast8_t count, ...)
{
  uint64_t result=0;
  va_list args;
  va_start (args, count);

  while(count--)
  {
    result=(result<<4) | va_arg(args,int);
  }
  return result;
}

uint_fast8_t  fakeArrayGet(uint64_t array, uint_fast8_t  index)
{
  return array>>(4*index)&0x0F;
}

uint64_t fakeArraySet(uint64_t array, uint_fast8_t index, uint_fast8_t value)
{
  array = array & ~((uint64_t)0x0F<<(4*index));
  array = array | ((uint64_t)value<<(4*index));
  return array;
}


unsigned getRandomDigits(void)
{
  uint64_t digits = fakeArrayCreate(9,9,8,7,6,5,4,3,2,1);
  uint_fast8_t left=4;
  unsigned result=0;
  while(left--)
  {
    uint_fast8_t digit=getRand(9-4+left);
    result=result*10+fakeArrayGet(digits,digit);
    //Move all digits above the selcted one 1 index down.
    //This removes the picked digit from the array.
    while(digit<8)
    {
      digits=fakeArraySet(digits,digit,fakeArrayGet(digits,digit+1));
      digit++;
    }
  }
  return result;
}

//Test our function
int main(int argc, char **argv)
  {
    srand(atoi(argv[1]));
    printf("%u\n",getRandomDigits());
  }
0

There are multiple answers to this question already, but none of them seem to fit the requirement only using ints, if, while and functions. Here is a modified version of Pelle Evensen's simple solution:

#include <stdlib.h>

int get_random_4digit(void) {
    int acc = 0, used = 0, i = 0;
    
    while (i < 4) {
        int idx = rand() % 9;  // Not strictly uniform but never mind...
        if (!(used & (1 << idx))) {
            acc = acc * 10 + idx + 1;
            used |= 1 << idx;
            i++;
        }
    }
    return acc;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189