在一定范围内生成 N 个随机且唯一的数字

问题描述 投票:0回答:8

使用 C# 在给定范围内生成 N 个唯一数字的有效方法是什么?例如,生成 1 到 50 之间的 6 个唯一数字。一种懒惰的方法是简单地在循环中使用

Random.Next()
并将该数字存储在数组/列表中,然后重复并检查它是否已经存在等。有没有更好的方法来生成一组随机但唯一的数字? 为了添加更多上下文,我想使用它们的索引从集合中选择 N 个随机项目。

谢谢

c# .net random unique
8个回答
17
投票

获取一个包含 50 个元素的数组:

{1, 2, 3, .... 50}
使用任意随机洗牌数组的标准算法对数组进行洗牌。修改后的数组的前六个元素就是您要查找的内容。 HTH


11
投票

对于 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();


8
投票

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

对于大量唯一数字,请将它们放入列表中..

6
投票

然后随机生成一个从1到myInts.Count的数字。存储

myInt
 值并将其从列表中删除。无需打乱列表,也无需查看该值是否已存在。

不要使用 

List

1
投票
Dictionary

!!

    
如果它对其他人有帮助,我更喜欢分配最少数量的必要物品。下面,我使用 HashSet,它确保新项目是唯一的。这也应该适用于非常大的集合,直到 HashSet 可以很好地发挥作用的极限。


0
投票

当您想要从大范围中选择几个数字时,请修改 Fisher-Yates shuffle 以跟踪交换的索引,而不是存储整个可能性数组。这将内存使用量减少到
O(numValues)

0
投票
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 的唯一随机数:

-1
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.