【问题标题】:Non repeating random numbers in Objective-CObjective-C 中的非重复随机数
【发布时间】:2010-12-09 17:05:21
【问题描述】:

我正在使用

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

但我每次都会重复。如何从范围中填写选择的int 值,以便在程序循环时不会受到任何欺骗?

【问题讨论】:

标签: objective-c random


【解决方案1】:

您可以使用Format-Preserving Encryption 来加密计数器。您的计数器只是从 0 向上,加密使用您选择的密钥将其转换为您想要的任何基数和宽度的看似随机的值。

块密码通常具有固定的块大小,例如64 位或 128 位。但是格式保留加密允许您使用像 AES 这样的标准密码,并使用您想要的任何基数和宽度(例如基数 2,宽度 16)制作更小宽度的密码,并且算法在密码学上仍然是稳健的。

保证永远不会发生冲突(因为加密算法会创建 1:1 映射)。它也是可逆的(2 路映射),因此您可以获取结果数字并返回您开始使用的计数器值。

AES-FFX 是一种建议的标准方法来实现这一点。我已经尝试了一些基于 AES-FFX 思想的基本 Python 代码,虽然不完全符合 --see Python code here。它可以例如将计数器加密为看似随机的 7 位十进制数或 16 位数字。

【讨论】:

    【解决方案2】:

    听起来你想要一个集合的洗牌而不是“真正的”随机性。只需创建一个所有位置都与数字匹配的数组并初始化一个计数器:

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

    然后,当你想要一个随机数时,使用以下方法:

    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.
    

    这将从一个不断减少的池中返回一个随机值,保证没有重复。当然,您必须小心池运行到零大小,并智能地重新初始化池。

    与保留已使用数字的列表并继续循环直到找到不在该列表中的数字相比,这是一种更具确定性的解决方案。随着池变小,这种算法的性能会下降。

    使用类似这样的静态值的 C 函数应该可以解决问题。调用它

    int i = myRandom (200);
    

    设置池(使用任何零或更大的数字指定大小)或

    int i = myRandom (-1);
    

    从池中获取下一个数字(任何负数都足够了)。如果函数无法分配足够的内存,它将返回 -2。如果池中没有剩余数字,它将返回 -1(此时您可以根据需要重新初始化池)。这是带有单元测试主要功能的功能供您试用:

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

    这是一次运行的输出:

    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
    

    请记住,因为它使用静态,如果他们想维护自己的独立池,从两个不同的地方调用是不安全的。如果是这种情况,静态变量将被替换为“属于”调用者的缓冲区(保存计数和池)(为此可以传入双指针)。

    而且,如果您正在寻找“多池”版本,为了完整起见,我将其包含在此处。

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

    从修改后的main() 可以看出,您需要首先初始化一个指向NULLint 指针,然后将其地址传递给myRandom() 函数。这允许每个客户端(代码中的位置)拥有自己的池,该池会自动分配和释放,但如果您愿意,您仍然可以共享池。

    【讨论】:

    • 实际上,当我要随机化 200 多个数字时,我已经想到了内存处理的问题。不过有一个问题,如果我要使用该数字调出图像,我该如何实现上述代码。 NSString *ImageName = [NSString stringWithFormat :@"%d.png,i]; 谢谢。
    • @Drahc,200 个数字并不多。我将添加一个 C 函数让您开始。
    • 大声笑。在这里,我担心内存消耗。感谢您的帮助,我在编程方面真的很年轻。
    • 没问题。希望我能在你对我的工作造成任何威胁之前退休 :-)
    • 哇,真快。谢谢pax,我稍后会试试这个。再次感谢。
    【解决方案3】:

    除了使用二级数组存储已经生成的随机数外,还调用了随机数。每次调用随机编号之前的播种功能。生成函数可能有助于生成不同的序列。每次运行的随机数。

    【讨论】:

      【解决方案4】:

      最好的方法是为已经使用的数字创建一个数组。创建随机数后,将其添加到数组中。然后当你去创建另一个随机数时,确保它不在使用的数字数组中。

      【讨论】:

        【解决方案5】:

        在不依赖外部随机过程(如放射性衰变或用户输入)的情况下,计算机将始终生成伪随机数——即具有随机数的许多统计特性的数字,但按顺序重复。

        这解释了通过随机播放随机化计算机输出的建议。

        丢弃以前使用的数字可能会人为地延长序列,但会牺牲统计数据,给人以随机的印象。

        【讨论】:

          【解决方案6】:

          您需要跟踪已使用的数字(例如,在数组中)。获取一个随机数,如果已经用过就丢弃。

          【讨论】:

          猜你喜欢
          • 2013-05-12
          • 2011-10-23
          • 2013-12-24
          • 1970-01-01
          • 2011-09-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多