繁体   English   中英

C中不重复数字的随机4位数字

[英]random 4 digit number with non repeating digits in C

我正在尝试创建一个get_random_4digit函数,该get_random_4digit生成一个 4 位数字,该数字具有 1-9 的非重复数字,同时仅使用int s、 ifwhile和函数,因此没有数组等。

这是我拥有的代码,但它并没有真正按预期工作,有人能指出我正确的方向吗?

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);
}

KISS 方法可能是这样的:

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;
}

这乍一看非常愚蠢。 快速分析表明,这确实还不错,对rand()的多次调用约为 4.9。

内循环步骤的预期数量[以及对rand()相应调用,如果我们假设rand() % 9为 iid] 将是:1 + 9/8 + 9/7 + 9/6 ~ 4.9107。

您可以使用线性反馈移位寄存器。 看看这个Computerphile 视频

您应该继续生成数字,直到找到不同的数字:

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; 
}

请自己摆弄

您可以在 9 位数的数组上使用部分 Fisher-Yates shuffle,在 4 位数后停止:

// 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 repeating digits.
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++) {
        int rand_pos = i + get_random_int(9 - i);
        char digit = possible[rand_pos];
        possible[rand_pos] = possible[i];
        possible[i] = digit; // not really needed
        result = result * 10 + digit; // update result
    }
    return result;
}

第一位数字有 9 种可能,第二位数字有 8 种可能,第三位数字有 7 种可能,最后一位数字有 6 种可能。 这适用于“ 9*8*7*6 = 3024排列”。

首先获取一个从 0 到 3023 的随机数。我们称之为P 要做到这一点而不会造成有偏差的分布,请使用类似do { P = rand() & 0xFFF; } while(P >= 3024); do { P = rand() & 0xFFF; } while(P >= 3024); .

第一个数字有 9 种可能,所以d1 = P % 9 + 1; P = P / 9; d1 = P % 9 + 1; P = P / 9; .

第二个数字有 8 种可能,所以d2 = P % 8 + 1; P = P / 8; d2 = P % 8 + 1; P = P / 8; .

第三个数字有 7 种可能,所以d3 = P % 7 + 1; P = P / 7; d3 = P % 7 + 1; P = P / 7; .

对于最后一位数字,您只需执行d4 = P + 1; 因为我们知道P不能太高。

下一个; 将“可能性”转换为数字。 对于d1你什么都不做。 对于d2 ,如果它大于或等于d1if(d2 >= d1) d2++;需要增加它,例如if(d2 >= d1) d2++; . d3d4执行相同操作(与之前的所有数字相比)。

最终的代码将类似于:

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;

    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;
}

一种方法是创建一个包含所有 9 位数字的数组,随机选择一个并将其从列表中删除。 像这样的东西:

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*9+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++;
  }
}

你说你需要一个没有数组的解决方案。 幸运的是,我们可以在单个uint64_t存储多达 16 个 4 位数字。 这是一个使用uint64_t存储数字列表的示例,因此不需要数组。

#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*9+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());
  }

一种变体可能是使用一个值0x123456789 ,并对半字节(构成十六进制值中一位数字的 4 位)进行 Fisher-Yates shuffle。 洗牌完成后,返回最低的 4 个半字节。

例子:

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

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

    // 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 &= (mask ^ (0xFULL << ish)) &
                (mask ^ (0xFULL << swp));
        bits |= (a << swp) | (b << ish);
    }
    return bits & 0xFFFF; // return the 4 lowest nibbles
}

位操作可能可以优化 - 但我按照我的想法写了它,所以保持原样可能更好可读性

然后,您可以将该值打印为十六进制值以获得所需的输出 - 或者提取 4 个半字节并将其转换为十进制输出。

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);
}

这个问题已经有多个答案,但似乎没有一个只使用int s, if , while和 functions满足要求。 这是Pelle Evensen简单解决方案的修改版本:

#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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM