使用 C# 在给定范围内生成 N 个唯一数字的有效方法是什么?例如,生成 1 到 50 之间的 6 个唯一数字。一种懒惰的方法是简单地在循环中使用
Random.Next()
并将该数字存储在数组/列表中,然后重复并检查它是否已经存在等。有没有更好的方法来生成一组随机但唯一的数字?
为了添加更多上下文,我想使用它们的索引从集合中选择 N 个随机项目。
谢谢
获取一个包含 50 个元素的数组:
{1, 2, 3, .... 50}
使用任意随机洗牌数组的标准算法对数组进行洗牌。修改后的数组的前六个元素就是您要查找的内容。 HTH
对于 50 个中的 6 个,我不太确定是否会担心效率,因为重复的可能性相对较低(根据我的粗略计算,总体为 30%)。你可以很容易地记住你之前生成的数字并将它们扔掉,就像(伪代码):
n[0] = rnd(50)
for each i in 1..5:
n[i] = n[0]
while n[1] == n[0]:
n[1] = rnd(50)
while n[2] == any of (n[0], n[1]):
n[2] = rnd(50)
while n[3] == any of (n[0], n[1], n[2]):
n[3] = rnd(50)
while n[4] == any of (n[0], n[1], n[2], n[3]):
n[4] = rnd(50)
while n[5] == any of (n[0], n[1], n[2], n[3], n[4]):
n[5] = rnd(50)
但是,当您从 50 中的 6 移动到 50 中的 48 或 6 中的 6 时,这种情况就会崩溃,因为重复的可能性开始变得越来越大。那是因为可用数字池变得越来越小,你最终会扔掉越来越多的数字。 对于一个非常有效的解决方案,为您提供值的子集,并且重复的可能性为零(并且没有不必要的预先排序),Fisher-Yates 是最佳选择。
dim n[50] // gives n[0] through n[9]
for each i in 0..49:
n[i] = i // initialise them to their indexes
nsize = 50 // starting pool size
do 6 times:
i = rnd(nsize) // give a number between 0 and nsize-1
print n[i]
nsize = nsize - 1 // these two lines effectively remove the used number
n[i] = n[nsize]
只需从池中选择一个随机数,将其替换为该池中的顶部数字,然后减小池的大小,您就可以进行洗牌,而不必担心预先进行大量交换。
如果数字很高,这一点很重要,因为它不会引入不必要的启动延迟。
例如,检查以下基准检查,从 10 中选择 10:
<------ n[] ------>
0 1 2 3 4 5 6 7 8 9 nsize rnd(nsize) output
------------------- ----- ---------- ------
0 1 2 3 4 5 6 7 8 9 10 4 4
0 1 2 3 9 5 6 7 8 9 7 7
0 1 2 3 9 5 6 8 8 2 2
0 1 8 3 9 5 6 7 6 6
0 1 8 3 9 5 6 0 0
5 1 8 3 9 5 2 8
5 1 9 3 4 1 1
5 3 9 3 0 5
9 3 2 1 3
9 1 0 9
您可以看到池子随着您的使用而减少,并且因为您总是用未使用的替换旧的,所以您永远不会重复。
使用返回的结果作为集合的索引将保证不会选择重复的项目。
var random = new Random();
var intArray = Enumerable.Range(0, 4).OrderBy(t => random.Next()).ToArray();
或
var intArray = Enumerable.Range(0, 10).OrderBy(t => random.Next()).Take(5).ToArray();
该数组将包含 5 个 0 到 10 之间的随机数。
int firstNumber = intArray[0];
int secondNumber = intArray[1];
int thirdNumber = intArray[2];
int fourthNumber = intArray[3];
int fifthNumber = intArray[4];
对于大量唯一数字,请将它们放入列表中..
然后随机生成一个从1到myInts.Count的数字。存储
myInt
值并将其从列表中删除。无需打乱列表,也无需查看该值是否已存在。
不要使用
List
Dictionary
!!
如果它对其他人有帮助,我更喜欢分配最少数量的必要物品。下面,我使用 HashSet,它确保新项目是唯一的。这也应该适用于非常大的集合,直到 HashSet 可以很好地发挥作用的极限。
当您想要从大范围中选择几个数字时,请修改 Fisher-Yates shuffle 以跟踪交换的索引,而不是存储整个可能性数组。这将内存使用量减少到
O(numValues)
numValues ≪ maxValueExclusive
时,这会更有效,但仍然确定性地终止。
public static int[] GetUnique(Random rng, int numValues, int maxValueExclusive)
{
if (maxValueExclusive < numValues)
{
throw new ArgumentOutOfRangeException(nameof(maxValueExclusive),
string.Format("Cannot select {0} unique values from a set containing {1} values", numValues, maxValueExclusive));
}
int[] result = new int[numValues];
Dictionary<int, int> valueMap = new();
for (int i = 0; i < numValues; ++i)
{
var randVal = rng.Next(maxValueExclusive);
result[i] = valueMap.GetValueOrDefault(randVal, randVal);
if (randVal != --maxValueExclusive)
valueMap[randVal] = valueMap.GetValueOrDefault(maxValueExclusive, maxValueExclusive);
}
return result;
}
生成从 1 到 40 的唯一随机数:
class Program
{
static int[] a = new int[40];
static Random r = new Random();
static bool b;
static void Main(string[] args)
{
int t;
for (int i = 0; i < 20; i++)
{
lab: t = r.Next(1, 40);
for(int j=0;j<20;j++)
{
if (a[j] == t)
{
goto lab;
}
}
a[i] = t;
Console.WriteLine(a[i]);
}
Console.Read();
}
}
示例输出:
7 38 14 18 13 29 28 26 22 8 24 19 号 35 39 33 32 20 2 15 37