C#范围内生成不重复随机数

范围为 [startNum, endNum] , 其中0<=startNum<endNum
生成的个数为needNum,并且needNum <= endNum - startNum + 1 因为C#的Random类不进行种子设置的话是伪随机,所以我们要改进一下Random类的Next方法,让它尽可能朝着真随机靠近。
优化后的代码如下:

 public static int Random(int starNum, int endNum)
 {
     byte [] randomBytes = new byte[4];
     RNGCryptoServiceProvider rngProvider = new RNGCryptoServiceProvider();
     rngProvider.GetBytes(randomBytes);
     Int32 iSeed = BitConverter.ToInt32(randomBytes, 0);
     Random random = new Random(iSeed);
     return random.Next(starNum, endNum + 1);
 }

 public static bool IsParameterValid(int startNum, int endNum, int needCount)
 {
     if (startNum < 0 || endNum <0 || startNum > endNum || needCount == 0 || needCount > endNum - startNum + 1)
     {
         return false;
     }
     return true;
 }
  • 方法一 使用两个数组,从第一个数组中随机位置抽取一个,放到第二个数组中,并且在第一个数组中删除这个值, 接下来从第一个数组的剩余数据中重复上面的步骤,直到第二个数组中获得了目标个数的值停止。 代码如下:

    public static List<int> PickMethod1(int startNum, int endNum, int needCount)
    {
        if (!IsParameterValid(startNum, endNum, needCount))
        {
            return null;
        }
        List<int> posList = new List<int>();
        List<int> newPosList = new List<int>();
        for (int i = 0; i <= endNum - startNum; i++)
        {
            posList.Add(i);
        }
        int count = 0;
        while (count < needCount)
        {
            int pickPos = Random(0, posList.Count -1);
            newPosList.Add(posList[pickPos]);
            posList.RemoveAt(pickPos);
            count++;
        }
        return newPosList;
    }
    

    优点:从剩余的数据中随机取值,不用判断重复,相对于方法二来说速度会更快,特别是needNum 很接近 endNum - startNum + 1的时候,会更加明显
    缺点:需要一个额外的数组,需要更多的内存

  • 方法二 使用HashTable,在一个循环中不停地在范围内随机的取值,并且检查检查这个值是否在HashTable中,如果不存在则存放到Hashtable中, 如果存在则重复前面的步骤,直到Hashtable的Count数量达到想要的数量值。
    代码如下:

    public static List<int> PickMethod2(int startNum, int endNum, int needCount)
    {
        if (!IsParameterValid(startNum, endNum, needCount))
        {
            return null;
        }
        Hashtable tb = new Hashtable();
        while (tb.Count < needCount)
        {
            int pickPos = Random(0, endNum - startNum);
            if (!tb.ContainsKey(pickPos))
            {
                tb.Add(pickPos, pickPos);
            }
        }
        List<int> posList = new List<int>();
        foreach (int key in tb.Keys)
        {
            posList.Add(key);
        }
        return posList;
    }
    

    优点:只需要一个Hashtable就可以解决,节省空间,需要的内存更少,算法也更简洁。
    缺点:每次随机获取一个数据,都需要判断是否已存在,这会加大计算量,特别是needNum 很接近 endNum - startNum + 1的时候,会更加明显

完整代码:

MathTools.cs