所以我有一个加权项目列表,我想从这个列表中选择4个非重复项目。
Item Weight
Apple 5
Banana 7
Cherry 12
...
Orange 8
Pineapple 50
最有效的方法是什么?我最初的尝试是,如果已经选择的项目出现的话,只需重新选择随后的选择......但是对于一个小的列表,这可能导致大量的重新登记。
编辑以澄清: 对于上面的例子,忽略水果D到N,总重量为82.所以先被挑选的机会是: 约6% B~8.5% C~14.6% O~9.8% P~61% 一旦选择了一个项目,概率就会(应该!)改变。
答案 0 :(得分:6)
在你的评论中,你说这种独特意味着:
我不想两次选择同一个项目。
..并且权重决定了被选中的可能性。
您需要做的就是确保不挑选重复项,只需在选择下一项之前从列表中删除最后一项。是的,这会略微改变您的权重,但如果您确实需要独特的结果,这是正确的统计变化。
另外,我不确定你是如何使用权重来确定候选者的,但是我想出了这个算法,应该用最少的循环来完成这个(并且不需要根据权重,可能导致非常大的数组,需要int权重等。)
我在这里使用了JavaScript,因此很容易在没有服务器的浏览器中看到输出。移植到PHP应该是微不足道的,因为它没有做任何复杂的事情。
var FRUITS = [
{name : "Apple", weight: 8 },
{name : "Orange", weight: 4 },
{name : "Banana", weight: 4 },
{name : "Nectarine", weight: 3 },
{name : "Kiwi", weight: 1 }
];
var PICKS = 3;
function getNewFruitsAvailable(fruits, removeFruit) {
var newFruits = [];
for (var idx in fruits) {
if (fruits[idx].name != removeFruit) {
newFruits.push(fruits[idx]);
}
}
return newFruits;
}
var results = [];
var candidateFruits = FRUITS;
for (var i=0; i < PICKS; i++) {
// CALCULATE TOTAL WEIGHT OF AVAILABLE FRUITS
var totalweight = 0;
for (var idx in candidateFruits) {
totalweight += candidateFruits[idx].weight;
}
console.log("Total weight: " + totalweight);
var rand = Math.random();
console.log("Random: " + rand);
// ITERATE THROUGH FRUITS AND PICK THE ONE THAT MATCHES THE RANDOM
var weightinc = 0;
for (idx in candidateFruits) {
// INCREMENT THE WEIGHT BY THE NEXT FRUIT'S WEIGHT
var candidate = candidateFruits[idx];
weightinc += candidate.weight;
// IF rand IS BETWEEN LAST WEIGHT AND NEXT WEIGHT, PICK THIS FRUIT
if (rand < weightinc/totalweight) {
results.push(candidate.name);
console.log("Pick: " + candidate.name);
// GET NEXT SET OF FRUITS (REMOVING PICKED FRUIT)
candidateFruits = getNewFruitsAvailable(candidateFruits, candidate.name);
break;
}
}
console.log("CandidateFruits: " + candidateFruits.length);
};
for (var i=0; i < results.length; i++) {
document.write(results[i] + "<br/>");
}
基本策略是将每个水果分配到总范围[0,1)
的一部分。在第一个循环中,你有这个:
脚本遍历列表中的每个项目,并进行权重计数器。当它到达包含第一个随机的范围时,它会选择该项目,将其从列表中删除,然后根据新的总重量重新计算范围并再次运行。
答案 1 :(得分:1)
function array_rand2($ary,$n = 1)
{
// make sure we don't get in to an infinite loop
// check we have enough options to select from
$unique = count(array_unique(array_keys($ary)));
if ($n > $unique) $n = count($unique);
// First, explode the array and expand out all the weights
// this means something with a weight of 5 will appear in
// in the array 5 times
$_ary = array();
foreach ($ary as $item => $weight)
{
$_ary = array_merge($_ary, array_fill(0, $weight, $item));
}
// now look for $n unique entries
$matches = array();
while (count($matches) < $n)
{
$r = $_ary[array_rand($_ary)];
if (!in_array($r,$matches))
{
$matches[] = $r;
}
}
// and now grab those $n entries and return them
$result = array();
foreach ($matches as $match){
$result[] = $match;
}
return $result;
}
看看这是否做得更好。
答案 2 :(得分:1)
Here I found the idea to following steps:
答案 3 :(得分:0)
也许不是“重新滚动”,你可以只增加你随机生成的列表元素索引:list.elementAt(rand_index++ % size(list))
(类似的东西)。我认为你会发现下一个随机的独特项目很快就会有这样的逻辑。
我确信有更好的解决方案,当然,通常有。
编辑:看起来Brad已经提供了一个.. :))