【问题标题】:Generate N random and unique numbers within a range在一个范围内生成 N 个随机且唯一的数字
【发布时间】:2011-05-16 23:53:32
【问题描述】:

使用 C# 在给定范围内生成 N 个唯一数字的有效方法是什么?例如,生成 1 到 50 之间的 6 个唯一数字。一种懒惰的方法是在循环中简单地使用 Random.Next() 并将该数字存储在数组/列表中,然后重复并检查它是否已经存在等等。是有更好的方法来生成一组随机但唯一的数字吗? 为了添加更多上下文,我想使用它们的索引从集合中选择 N 个随机项目。

谢谢

【问题讨论】:

  • @drachenstern:今天有点敏感,不是吗? @Tim - 抱歉,我没有搜索“子集”关键字,最终得到了为什么随机数生成很难的结果。感谢您的链接。
  • ~ 我不这么认为。我觉得这个问题很好解决。其他人显然同意我的观点。
  • @drachenstern - 与你所说的无关。只是想知道是否真的需要讽刺。

标签: c# .net random unique


【解决方案1】:

获取一个包含 50 个元素的数组:{1, 2, 3, .... 50} 使用随机洗牌数组的任何标准算法来洗牌。修改后的数组的前六个元素就是您要查找的内容。高温

【讨论】:

  • @HPT:当然,在这种情况下,最好保留一组已有的数字。但是在这种情况下,当数字很小时,我相信我的解决方案一点也不差
  • @armen:正确的解决方案是一回事,好的解决方案是另一回事。你也可以写一个随机生成器作为这个问题提到的问题!但这不是解决方案。
  • @HPT:至少我的解决方案具有可预测的运行时间
  • @至少 O(n) 内存使用和 O(n) 运行时! n : 表示范围
  • 此解决方案可能适用于 6-from-50,因为随机排序 50 个项目可能不是杀手。但是,再一次,简单的记住并丢弃重复方法也是如此,因为重复的机会并不高。为了获得更有效的答案(没有预先排序,保证没有重复),您希望按照我的答案进行 Fisher-Yates 洗牌,但 可能 在这里实际上不是必需的 - 您必须针对问题的解决方案.
【解决方案2】:

对于 6-from-50,我不太确定是否会担心效率,因为重复的可能性相对较低(根据我粗略的计算,总体而言为 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)

但是,当您从 6-from-50 移动到 48-from-50 或 6-from-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-from-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

您可以看到池在减少,因为您总是用未使用的替换使用过的,因此您永远不会重复。

使用返回的结果作为集合中的索引将保证不会选择重复的项目。

【讨论】:

  • 这是最好的解决方案,是我的改进版。
  • 如果范围很大,可能无法将所有数字存储在内存中
【解决方案3】:
var random = new Random();
var intArray = Enumerable.Range(0, 4).OrderBy(t => random.Next()).ToArray();

这个数组将包含从 0 到 4 的 5 个随机数。

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

【讨论】:

  • 第一个是一个非常简洁的解决方案。谢谢!
【解决方案4】:

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

        Random random = new Random();
        List<int> uniqueInts = new List<int>(10000);
        List<int> ranInts = new List<int>(500);
        for (int i = 1; i < 10000; i++) { uniqueInts.Add(i); }

        for (int i = 1; i < 500; i++)
        {
            int index = random.Next(uniqueInts.Count) + 1;
            ranInts.Add(uniqueInts[index]);
            uniqueInts.RemoveAt(index);
        }

然后随机生成一个从 1 到 myInts.Count 的数字。存储myInt 值并将其从列表中删除。无需打乱列表,也无需查看该值是否已存在。

【讨论】:

  • 从列表中删除数字(无论您想使用什么IEnumerable)使其独一无二,如果它不存在,您将无法从列表中获取它...再次这样更快对于 LARGE 的唯一数字集,因为您不是在寻找可能存在或不存在的数字。
  • 非常适合我。只有一个小错误:它应该是 uniqueInts.RemoveAt(index);而不是 uniqueInts.Remove(index);
  • @AyKarsi,这是一个非常重要的说明,谢谢! (更新)
【解决方案5】:

不要使用List,而是使用Dictionary!!

【讨论】:

  • 另外你应该检查添加N成功添加,很明显如何检查你是否在字典中添加了新值!
  • 感谢您的提示。我想字典对于更大的集合会更好。
【解决方案6】:

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

    public static IEnumerable<int> GetRandomNumbers(int numValues, int maxVal)
    {
        var rand = new Random();
        var yieldedValues = new HashSet<int>();

        int counter = 0;
        while (counter < numValues)
        {
            var r = rand.Next(maxVal);
            if (yieldedValues.Add(r))
            {
                counter++;
                yield return r;
            }
        }
    }

【讨论】:

    【解决方案7】:

    生成从 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

    【讨论】:

    • 非常低效的解决方案......尤其是当已经提出了更好的答案时。您还获得了未使用的属性,并且使用了 Goto!为什么?
    猜你喜欢
    • 2014-05-15
    • 2011-08-02
    • 2018-12-17
    • 2018-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-25
    相关资源
    最近更新 更多