假设我们有从 1 到 25 的数字,我们必须选择 15 个数字的集合。
如果我是对的,可能的集合是 3268760。
在这 3268760 个选项中,您必须生成 100000 个
生成 100000 个唯一且随机的子集的最佳方法是什么?
有没有办法,一种算法来做到这一点?
如果不是,那么检测重复项的最佳选择是什么?
我打算在 PHP 上执行此操作,但一个通用的解决方案就足够了,任何不涉及太多“学术”(更实用)的参考都会对我有很大帮助。
假设我们有从 1 到 25 的数字,我们必须选择 15 个数字的集合。
如果我是对的,可能的集合是 3268760。
在这 3268760 个选项中,您必须生成 100000 个
生成 100000 个唯一且随机的子集的最佳方法是什么?
有没有办法,一种算法来做到这一点?
如果不是,那么检测重复项的最佳选择是什么?
我打算在 PHP 上执行此操作,但一个通用的解决方案就足够了,任何不涉及太多“学术”(更实用)的参考都会对我有很大帮助。
有一种方法可以生成随机的子集样本,保证没有重复,使用 O(1) 存储,并且可以随时重新生成。首先,编写一个函数来生成一个给定词法索引的组合。其次,使用第一个 Combin(n, m) 整数的伪随机排列以随机顺序逐步遍历这些组合。只需将数字 0...100000 输入到排列中,使用排列的输出作为组合生成器的输入,然后处理结果组合。
它们必须是真正随机的吗?还是看似随机?
选择:生成一个包含所有 25 个元素的集合 - 使用 Fisher-Yates / Knuth 洗牌“洗牌”前 15 个元素,然后检查您之前是否见过前 15 个元素的排列。如果是这样,请忽略并重试。
重复:您有 25 个值是否存在 - 这可以简单地散列为整数值(如果第一个元素存在,则添加 2^0,如果第二个元素存在,则添加 2^1 等 - 它可以是直接表示为 25 位数字),因此您可以轻松检查是否已经看过它。
你会遇到相当多的冲突,但如果它不是性能关键的片段,它可能是可行的。
您环境的随机数生成器 (RNG) 将为您提供均匀分布在特定范围内的随机数。这种类型的分布通常是需要的,例如,如果您的子集模拟彩票抽奖,但重要的是要提及这一事实,以防您在建模时说在中学范围内发现的人的年龄......
鉴于此 RNG,您可以“绘制” 10(或 15,请阅读下文)介于 1 和 25 之间的数字。这可能需要您乘以(并四舍五入)生成器产生的随机数,并且忽略大于 25 的数字(即再次绘制),这取决于与 RNG 关联的确切 API,但再次在给定范围内获取绘图是微不足道的。当数字再次出现时,您还需要重新绘制。
我建议您只获取 10 个数字,因为这些可以从 1-25 完整序列中删除以产生一组 15。换句话说,放入 15 的绘图与取出的 10 相同...
接下来,您需要断言集合的唯一性。您可以使用散列来唯一标识每个集合,而不是存储整个集合。这应该占用少于 25 位,因此可以存储在 32 位整数上。然后,您需要高效存储多达 100,000 个这些值;除非您想将其存储在数据库中。
在这个从所有可能的集合中取出 100,000 个集合的唯一性问题上,碰撞的概率似乎相对较低。编辑:哎呀...我很乐观...这个概率不是那么低,大约有 1.5% 的机会在绘制第 50,000 个之后开始发生碰撞,会有相当多的碰撞,足以保证系统将它们排除在外...
这是基于 mjv 的答案的 PHP 解决方案,这就是我的想法。如果你运行它完整的 100k 组,你确实会看到很多碰撞。但是,我很难设计一个系统来避免它们。相反,我们只是相当快地检查它们。
我会考虑更好的解决方案……在这台笔记本电脑上,我可以在 5 秒内完成 10k 套,在 20 秒内完成 20k 套。100k 需要几分钟。
这些集合表示为(32 位)整数。
<?PHP
/* (c) 2009 tim - anyone who finds a use for this is very welcome to use it with no restrictions unless they're making a weapon */
//how many sets shall we generate?
$gNumSets = 1000;
//keep track of collisions, just for fun.
$gCollisions = 0;
$starttime = time();
/**
* Generate and return an integer with exactly 15 of the lower 25 bits set (1) and the other 10 unset (0)
*/
function genSetHash(){
$hash = pow(2,25)-1;
$used = array();
for($i=0;$i<10;){
//pick a bit to turn off
$bit = rand(0,24);
if (! in_array($bit,$used)){
$hash = ( $hash & ~pow(2,$bit) );
$i++;
$used[] = $bit;
}
}
return $hash;
}
//we store our solution hashes in here.
$solutions = array();
//generate a bunch of solutions.
for($i=0;$i<$gNumSets;){
$hash = genSetHash();
//ensure no collisions
if (! in_array($hash,$solutions)){
$solutions[] = $hash;
//brag a little.
echo("Generated $i random sets in " . (time()-$starttime) . " seconds.\n");
$i++;
}else {
//there was a collision. There will generally be more the longer the process runs.
echo "thud.\n";
$gCollisions++;
}
}
// okay, we're done with the hard work. $solutions contains a bunch of
// unique, random, ints in the right range. Everything from here on out
// is just output.
//takes an integer with 25 significant digits, and returns an array of 15 numbers between 1 and 25
function hash2set($hash){
$set = array();
for($i=0;$i<24;$i++){
if ($hash & pow(2,$i)){
$set[] = $i+1;
}
}
return $set;
}
//pretty-print our sets.
function formatSet($set){
return "[ " . implode(',',$set) . ']';
}
//if we wanted to print them,
foreach($solutions as $hash){
echo formatSet(hash2set($hash)) . "\n";
}
echo("Generated $gNumSets unique random sets in " . (time()-$starttime) . " seconds.\n");
echo "\n\nDone. $gCollisions collisions.\n";
我认为这一切都是正确的,但为时已晚,我一直在享用几瓶非常棒的啤酒。