【问题标题】:Five unique, random numbers from a subset一个子集中的五个唯一随机数
【发布时间】:2011-03-01 11:58:07
【问题描述】:

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

我当前的方法是从一个子集中减去最后五个数字简单地生成五个随机数。如果任何数字相互匹配,则它们将转到子集末尾的各自位置。因此,如果第四个数字与任何其他数字匹配,它将设置为从最后一个数字开始的第四个。

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

请记住,这是一个好奇心,而不是一些关键任务问题。如果每个人都没有发布“您为什么遇到这个问题?”,我将不胜感激。答案。我只是在寻找想法。
非常感谢!

【问题讨论】:

  • 我的意思是我试图避免整个while 循环方法,其中有人在while 循环中生成数字,直到他们得到一个唯一的数字。我知道获得重复数字的可能性很小,但这是一种好奇心,我并不是在寻找一个假设执行时间无限的解决方案。
  • 你意识到它花费无限时间的可能性不是很大:p
  • 是的,我同意,但就像我说的那样,我不太担心找到一个可用的解决方案,因为我已经有了一个。我正在寻找最好的解决方案或我尚未阅读或想到的东西。如果我只想满足于“足够好”的东西,问一个问题有什么意义?我希望扩大我对如何解决几个问题的了解,而不是一遍又一遍地做同样的“它有效”的解决方案。
  • @Col。弹片:我已经从这篇文章中学到了一些新方法,所以我认为值得问一下,而不是因为尝试而受到侮辱。

标签: php algorithm random


【解决方案1】:

一个随机数调用就足够了。

如果你想在 1-n 范围内选择 5 个唯一数字的子集,则在 1 到 (n 选择 r) 中选择一个随机数。

保持从 1 到(n 选择 r)到可能的 5 个元素子集的 1-1 映射,您就完成了。这个映射是标准的,可以在网上找到,例如: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}。

【讨论】:

  • 有趣的答案。虽然我找不到这种 1-1 映射的解释!也许我无法找出好的搜索词。愿意在您的回答中用一两个网址帮助我们吗?
  • 10000 选择 5 是 832500291625002000,比 PHP_INT_MAX 大很多。不过,对于有趣的答案 +1。
  • 更正:它在 64 位机器上并不大,但我的观点是,对于 20k 来说它会更大。
  • @tau:我已经编辑了答案以添加一些解释。如果您需要更多说明,请告诉我。
【解决方案2】:

您最好的选择是循环,例如:

$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(取4) 1 2 3 4 5 6 7 8 9(给 4) 1 2 3 4 5 6 7 8 (拍5) 1 2 3 5 6 7 8 9(给 6) 1 2 3 4 5 6 7 (取6) 1 2 3 5 7 8 9(给出 8) 1 2 3 4 5 6(取 5 个) 1 2 3 5 7 9(给出 7) 例如,最后一次提取: x = 5 x >= 4? x == 6 x >= 6? x == 7 x >= 8? x == 7

【讨论】:

  • 我认为你的循环想法是他特别想避免的,而你的最后一个想法是对他问题的正确回答。
  • 我认为您的第二种方法不会防止重复。例如,我可以生成 10,然后是 9,因此我有 10、10。
  • @erisco 我已经(希望)修复了它。
  • @Col。弹片 他,也许他想要一些东西来证明完全正确。
  • @Artefacto:看起来根据大小使用其中一种方法可以为问题提供强大的解决方案,正如 aioobe 指出的那样,Python 已经做到了。感谢您的提示。
【解决方案3】:

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

据我所知,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】:

    由于您只是在寻找不同的想法,这里有一个:

    拨打Random.org 生成您需要的随机数集。

    【讨论】:

    • ...是的,我意识到,如果您不愿意承担 while 循环的成本,您很可能不愿意通过网络为您的号码拨打电话,但您确实说过想要不同的想法。
    • 相信我,我很欣赏至少提出一种新颖的解决方案的多样性。
    【解决方案5】:

    如果您知道大小 N,则以 5/N 的概率保留每个数字,生成一个介于 0 和 1 之间的随机数,如果小于 5/N,则保留该项目。当我们有 5 个项目时停止。

    如果我们不知道 N,请使用 resorvoir sampling

    【讨论】:

    • 您能举个例子吗?我不确定我是否跟随。
    • 在 Knuth 的第 2 卷中寻找储层采样,请阅读该部分。该部分包含这两种算法。
    【解决方案6】:

    Artefacto 上面第二个解决方案在 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);
        }
    }
    

    【讨论】:

      【解决方案7】:

      我知道我们正在努力避免循环,但为了以防万一这对某人有所帮助,您可以使用 HashSet 而不是 List。这对于很少发生冲突的稀疏集合非常有效。

      var hs = new HashSet<int>();
      var rand = new Random();
      for(int i=0; i<10000; i++)
      {
        int n;
        while(true)
        {
          n = rand.Next(0, 10000000);
          if(!hs.Contains(n)) {break;}
        }
        hs.Add(n);
      }
      

      【讨论】:

        猜你喜欢
        • 2012-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-09
        • 1970-01-01
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        相关资源
        最近更新 更多