代码之家  ›  专栏  ›  技术社区  ›  tau

一个子集中的五个唯一随机数

  •  4
  • tau  · 技术社区  · 15 年前

    我知道类似的问题经常出现,可能没有明确的答案,但我想从一个可能是无限的数字子集(可能是0-20或0-1000000)中生成五个唯一的随机数。
    唯一的问题是我不想跑 while 循环或填充数组。

    我目前的方法是简单地从一个子集减去最后五个数字生成五个随机数。如果其中任何一个数字相互匹配,那么它们将在子集的末尾到达各自的位置。因此,如果第四个号码与任何其他号码匹配,它将下注设置为最后一个号码的第四个。

    是否有人有“足够随机”的方法,并且不涉及昂贵的循环或数组?

    请记住这是一个好奇心,而不是一些关键任务的问题。如果每个人都不贴“你为什么有这个问题?”我会很感激的。答案。我只是在想办法。
    谢谢!

    6 回复  |  直到 12 年前
        1
  •  8
  •   Aryabhatta    15 年前

    一个随机号码呼叫就足够了。

    如果要选择范围1-n中5个唯一数字的子集,请选择1到(n选择r)中的随机数字。

    保持从1到(n选择r)的1-1映射到可能的5元素子集集,这样就完成了。此映射是标准的,可以在Web上找到,例如: http://msdn.microsoft.com/en-us/library/aa289166%28VS.71%29.aspx

    举个例子:

    考虑从五个数字中生成两个数字的子集的问题:

    _1,…,5的可能2元素子集是

    1. {1,2}
    2. {1,3}
    3. {1,4}
    4. {1,5}
    
    5. {2,3}
    6. {2,4}
    7. {2,5}
    
    8. {3,4}
    9. {3,5}
    
    10. {4,5}
    

    现在5选择2等于10。

    所以我们选择一个从1到10的随机数。说我们得了8。现在我们按照上面的顺序生成第8个元素:它给出3,4,所以您需要的两个数字是3和4。

    我链接到的msdn页面向您展示了一个生成集合的方法,给出了这个数字。即给定8,则返回集合3,4。

        2
  •  4
  •   Artefacto    15 年前

    您的最佳选择是循环,如:

    $max = 20;
    $numels = 5;
    $vals = array();
    while (count($vals) < $numels) {
        $cur = rand(0, $max);
        if (!in_array($cur, $vals))
            $vals[] = $cur;
    }
    

    对于小范围,可以使用 array_rand :

    $max = 20;
    $numels = 5;
    $range = range(0, $max);
    $vals = array_rand($range, $numels);
    

    您还可以生成一个介于0和max之间的数字,另一个介于0和max-1之间的数字,…介于0和max-4之间。然后将x和为第n个生成的数字,其中x是按这种方式计算的数字:

    • 取第n次迭代中生成的数字并将其赋给x
    • 如果它大于或等于第一次迭代中生成的,则递增
    • 如果这个新数字大于或等于在第二次迭代中生成(并更正)的数字,则递增它。
    • 如果这个新数字大于或等于第(n-1)次迭代中生成(并修正)的数字,则递增它。

    映射如下:

    1 2 3 4 5 6 7 8 9 (take 4)
    1 2 3 4 5 6 7 8 9 (gives 4)
    
    1 2 3 4 5 6 7 8 (take 5)
    1 2 3 5 6 7 8 9 (gives 6)
    
    1 2 3 4 5 6 7 (take 6)
    1 2 3 5 7 8 9 (gives 8)
    
    1 2 3 4 5 6 (take 5)
    1 2 3 5 7 9 (gives 7)
    
    example, last extraction:
    x = 5
    x >= 4? x == 6
    x >= 6? x == 7
    x >= 8? x == 7
    
        3
  •  2
  •   aioobe    15 年前

    这个问题的一般形式真的很有趣。应该从一个元素池中选择(并将它们从池中删除)还是应该在点击一个已经占用的元素时循环?

    据我所知,Random.sample的python库实现 运行时选择 这两种方法之间的比例取决于输入列表的大小和要选择的元素的数目。

    源代码中的注释:

        # When the number of selections is small compared to the
        # population, then tracking selections is efficient, requiring
        # only a small set and an occasional reselection.  For
        # a larger number of selections, the pool tracking method is
        # preferred since the list takes less space than the
        # set and it doesn't suffer from frequent reselections.
    

    但是,在OP提到的特定实例中(选择5个数字),我认为循环“同时命中所取的数字”是可以的,除非伪随机生成器被破坏。

        4
  •  0
  •   Robert Groves    15 年前

    既然你只是在寻找不同的想法,这里有一个:

    呼喊 Random.org 生成所需的一组随机数。

        5
  •  0
  •   Fakrudeen    15 年前

    如果你知道大小n,那么保持每个数字的概率为5/n,生成一个介于0和1之间的随机数,如果它小于5/n,则保持该项。当我们有5件物品时停止。

    如果我们不知道使用 resorvoir sampling .

        6
  •  0
  •   Luke Gumbley    12 年前

    Artefactor上述第二个解决方案在C中的实现,作为ICollection的助手和扩展方法:

    static class Program {
    
        public static IEnumerable<int> Subset(int max) {
            Random random = new Random();
            List<int> selections = new List<int>();
            for (int space = max; space > 0; space--) {
                int selection = random.Next(space);
                int offset = selections.TakeWhile((n, i) => n <= selection + i).Count();
                selections.Insert(offset, selection + offset);
                yield return selection + offset;
            }
        }
    
        public static IEnumerable<T> Random<T>(this ICollection<T> collection) {
            return Subset(collection.Count).Select(collection.ElementAt);
        }
    
        static void Main(string[] args) {
            Subset(10000).Take(10).ToList().ForEach(Console.WriteLine);
            "abcdefghijklmnopqrstuvwxyz".ToArray().Random().Take(5).ToList().ForEach(Console.WriteLine);
        }
    }