Php 以概率选择随机条目
假设我在数据库中存储了以下信息:Php 以概率选择随机条目,php,mysql,arrays,Php,Mysql,Arrays,假设我在数据库中存储了以下信息: User Points A 2000 B 1000 我想根据分数的多少随机选择一个赢家。在这种情况下,由于总共有3000分,“a”有67%的几率被选中,“B”有33%的几率被选中 使用PHP选择获胜者最有效的方法是什么(从计算概率到选择获胜者)?请注意,播放的用户数量不是固定的,可能会达到很大的数量(因此它应该计算“每个用户”,而不是固定在a和B上) 我一直在玩弄可能的解决方案,但还没有找到答案。
User Points
A 2000
B 1000
我想根据分数的多少随机选择一个赢家。在这种情况下,由于总共有3000分,“a”有67%的几率被选中,“B”有33%的几率被选中
使用PHP选择获胜者最有效的方法是什么(从计算概率到选择获胜者)?请注意,播放的用户数量不是固定的,可能会达到很大的数量(因此它应该计算“每个用户”,而不是固定在a和B上)
我一直在玩弄可能的解决方案,但还没有找到答案。我很想听听你的解决方案 只是一个想法(参考我关于机会的评论):将所有分数相加,然后对球员进行排序。然后选择一个介于1和$sum
之间的随机数。现在你可以从你的随机数中减去玩家的点数,直到你达到0
$players = array(
"A" => 2000,
"B" => 1000
);
$sum = array_sum($players);
echo $random = rand(1, $sum)."\n";
foreach($players as $player => $points) {
$winner = $player;
$random -= $points;
if($random <= 0)
break;
}
echo $winner."\n";
$players=array(
“A”=>2000,
“B”=>1000
);
$sum=数组和($players);
echo$random=rand(1,$sum)。“\n”;
foreach($player作为$player=>$points的玩家){
$winner=$player;
$random-=$points;
如果($random你可以在所有玩家身上使用公式1/p。我认为规范化它是个好主意,这样所有概率的总和都是1。然后你可以使用[0…1]中的随机生成器然后循环遍历所有玩家。当随机生成器中的数字小于当前玩家概率时,选择该玩家,否则从随机数字中减去当前概率,然后移动到下一个玩家:
x = random([0.0, 1.0])
for i in 0..n
if x < probabilities[i]
choose(i)
break
else
x -= probabilities[i]
end
end
x=random([0.0,1.0])
对于0..n中的i
如果x<概率[i]
选择(一)
打破
其他的
x-=概率[i]
结束
结束
当你需要规格化时,你必须用所有1/px乘法器之和的reziproken乘以每1/px。例如:你有一个顶点,有两条边p1=30,p2=15.1/(1/30+1/15)=10,P1=10*1/30=1/3和P2=10*1/15=2/3。基于处理用户和点的整个地图,可能有许多类似的方法。这很简单,如果用户不多,也可以很好地工作。但是当用户数量增加时,可能会在内存甚至CPU使用方面出现很大的性能问题。因此,我认为考虑性能的可能解决方案
下面描述了我将根据概率绘制用户的方法:
+------+--------+
| User | Points | Bar graph:
+------+--------+
| A | 20 | |~~~~~~~~~~~~~~~~~~~~|
+------+--------+
| B | 10 | |~~~~~~~~~~|
+------+--------+
| C | 1 | |~|
+------+--------+
| D | 5 | |~~~~~|
+------+--------+
| E | 12 | |~~~~~~~~~~~~|
+------+--------+
| F | 8 | |~~~~~~~~|
+------+--------+
TOTAL | 56 | Random number: 33
+--------+
If we take all bars and put them heel and toe we get something like this:
A B C D E F
|~~~~~~~~~~~~~~~~~~~~|~~~~~~~~~~|~|~~~~~|~~~~~~~~~~~~|~~~~~~~~|
Position 33 is up here ▲ so we got a winner: user D
这是一个简单的概念,但它可能会破坏性能,这取决于算法实现(例如,如果我们尝试按顺序执行)
我要做的是将这组用户平分,并将第一部分的分数和随机数进行比较。如果数字小于第一部分的分数(累计)总之,数字必须对应于该部分中的一个用户,如果不是,则数字对应于第二部分。此方法必须递归应用于每个选定部分,直到我们得到一个用户(获胜者).这样一来,我们在第一次迭代中丢弃了总用户数的1/2,在第二次迭代中丢弃了1/4,依此类推。这意味着,如果我们有100万用户,在第二次迭代中,我们将丢弃75万。在我看来还不错
下面是基于PHP和MySQL的解决方案
用户表:
CREATE TABLE `User` (
`id` int(15) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`points` int(15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;
数据库交互类:
class UserDb
{
private $pdo;
private $sumIntervalStmt;
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
$this->sumIntervalStmt = null;
}
public function getTotal()
{
$query = 'SELECT COUNT(`id`) FROM `User`;';
$result = $this->pdo->query($query);
return (int)($result->fetchColumn());
}
public function getTotalPoints()
{
$query = 'SELECT SUM(`points`) FROM `User`;';
$result = $this->pdo->query($query);
return (int)($result->fetchColumn());
}
public function sumPointsInterval($offset, $length)
{
if ($this->sumIntervalStmt === null) {
$this->sumIntervalStmt = $this->pdo->prepare(
'SELECT SUM(points) FROM (' .
'SELECT points FROM `User` LIMIT ?, ?' .
') AS Subgroup;'
);
}
$this->sumIntervalStmt->bindValue(1, (int)$offset, \PDO::PARAM_INT);
$this->sumIntervalStmt->bindValue(2, (int)$length, \PDO::PARAM_INT);
$this->sumIntervalStmt->execute();
return (int)($this->sumIntervalStmt->fetchColumn());
}
public function getUserByOffset($offset)
{
$query = 'SELECT * FROM `User` LIMIT ?, 1;';
$stmt = $this->pdo->prepare($query);
$stmt->bindValue(1, (int)$offset, \PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchObject();
}
}
用户抽奖类别:
class UserRaffle
{
private $users;
public function __construct(UserDb $users)
{
$this->users = $users;
}
public function drawUser()
{
$total = $this->users->getTotal();
$number = rand(1, $this->users->getTotalPoints());
$offset = 0;
$length = ceil($total / 2);
$count = $total;
$sum = $this->users->sumPointsInterval($offset, $length);
$accum = 0;
while ($count > 1) {
if ($number <= $sum) {
$count -= $count - $length;
$length = ceil($length / 2);
$interval = $this->users->sumPointsInterval($offset, $length);
$sum = $accum + $interval;
} else {
$accum += $sum;
$offset += $length;
$count -= $length;
$length = ceil($count / 2);
$interval = $this->users->sumPointsInterval($offset, $length);
$sum += $interval;
}
}
return $this->users->getUserByOffset($offset);
}
}
你真的想分配这些机会吗?或者你是说67%和33%?也许你可以看看这里:这里:)@Michael这不是OP的问题。@AmShaeger哇,这是我这边的一个数学错误:)这很好用,我可以使用while()将其用于SQL条目而不是foreach!谢谢!如果你有10万用户呢?你会运行一个返回10万行的查询吗?问题是用PHP来解决这个问题。MySQL查询可能有更好的方法来解决这个问题,但这并没有被问到。@AmShaegar是的,但OP说用户“可以范围很大”所以这个解决方案不太合适。哇,这是令人惊讶的好解释和好代码!非常感谢!
$pdo = new \PDO('mysql:dbname=test;host=localhost', 'username', '********');
$users = new UserDb($pdo);
$raffle = new UserRaffle($users);
$winner = $raffle->drawUser();