ユーザーとポイントのマップ全体を処理することに基づいて、多くの同様のアプローチが考えられます。これは簡単で、多くのユーザーがいない場合は問題なく機能します。しかし、ユーザー数が増えると、メモリや 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
これは単純な概念ですが、アルゴリズムの実装によってはパフォーマンスが低下する可能性があります (たとえば、順次実行しようとすると)。
ここで行うことは、ユーザーのグループを二等分し、最初の部分のポイントの合計を乱数と比較することです。数が最初の部分のポイント (累積) 合計よりも小さい場合、その数はその部分内のユーザーに対応している必要があり、そうでない場合、数は 2 番目の部分に対応しています。このメソッドは、1 人のユーザー (勝者) が得られるまで、選択した各パーツに再帰的に適用する必要があります。このようにして、最初の反復でユーザーの合計量の 1/2 を破棄し、2 回目の反復で 1/4 を破棄する、というようにします。これは、100 万人のユーザーがいた場合、2 回目の反復で 750k を取り除くことを意味します。悪くないIMO。
これが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);
}
}
そして実行:
$pdo = new \PDO('mysql:dbname=test;host=localhost', 'username', '********');
$users = new UserDb($pdo);
$raffle = new UserRaffle($users);
$winner = $raffle->drawUser();