「從 1-100 取 10 個不重複隨機數」、「洗牌發牌」、「抽獎系統去重」是高頻需求。本文系統梳理 6 種主流實現,從簡單到高效,從基礎庫函數到底層算法,附帶性能對比與陷阱避雷。
[min, max]count,要求 count <= max - min + 1<?php
function uniqueRandShuffle($min, $max, $count) {
if ($count > $max - $min + 1) throw new InvalidArgumentException('數量超過範圍');
$pool = range($min, $max); // 建立完整池
shuffle($pool); // 原地洗牌(Fisher-Yates)
return array_slice($pool, 0, $count); // 取前 count 個
}
// 使用
print_r(uniqueRandShuffle(1, 100, 10));
// Array ( [0] => 42 [1] => 17 [3] => 89 ... )
// 大範圍優化:避免構建巨大數組
function uniqueRandShuffleOptimized($min, $max, $count) {
$pool = range($min, $max);
// 部分洗牌:只打亂前 count 位(Fisher-Yates 變體)
for ($i = 0; $i < $count; $i++) {
$j = mt_rand($i, count($pool) - 1);
[$pool[$i], $pool[$j]] = [$pool[$j], $pool[$i]];
}
return array_slice($pool, 0, $count);
}
?>
優點:代碼極簡、語義明確、均勻隨機、PHP 內建 shuffle 經優化。
缺點:range(1, 10000000) 會佔用大量內存(約 80MB),大範圍慎用。
<?php
function uniqueRandLoop($min, $max, $count) {
$result = [];
while (count($result) < $count) {
$result[] = mt_rand($min, $max);
$result = array_unique($result); // 去重
}
return array_values($result); // 重置鍵名
}
// 缺點:隨著結果集增大,碰撞概率指數級上升,極慢
?>
<?php
function uniqueRandFlip($min, $max, $count) {
$result = [];
while (count($result) < $count) {
$result[] = mt_rand($min, $max);
// 翻翻法:key<->value 交換兩次,利用鍵名唯一性去重
$result = array_flip(array_flip($result));
}
shuffle($result); // 打亂順序、重置鍵名
return $result;
}
// 經驗:array_flip 去重比 array_unique 快 10-50 倍
?>
array_is_list、array_unique 優化)<?php
function uniqueRandSet($min, $max, $count) {
$set = [];
while (count($set) < $count) {
$set[mt_rand($min, $max)] = true; // 鍵名天然去重
}
return array_keys($set);
}
// 優點:無需去重函數調用,直接利用哈希表特性,最快
?>
<?php
/**
* 從 [min, max] 範圍均勻抽取 count 個不重複數
* 適用:範圍極大(如 1-1億)、內存受限、流式數據
* 算法:Reservoir Sampling Algorithm R
*/
function uniqueRandReservoir($min, $max, $count) {
$range = $max - $min + 1;
if ($count > $range) throw new InvalidArgumentException('數量超過範圍');
$reservoir = [];
// 前 count 個直接放入
for ($i = 0; $i < $count; $i++) {
$reservoir[] = $min + $i;
}
// 第 i 個元素(i 從 count 開始)以 count/i 概率替換水塘中隨機位置
for ($i = $count; $i < $range; $i++) {
$j = mt_rand(0, $i);
if ($j < $count) {
$reservoir[$j] = $min + $i;
}
}
shuffle($reservoir);
return $reservoir;
}
// 測試:從 1 億取 1000 個,瞬間返回,內存極小
$result = uniqueRandReservoir(1, 100000000, 1000);
print_r(array_slice($result, 0, 10));
?>
<?php
function randomCharset($length, $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
$bytes = random_bytes($length); // 密碼學安全隨機字節
$result = '';
$clen = strlen($charset);
for ($i = 0; $i < $length; $i++) {
$result .= $charset[$bytes[$i] % $clen];
}
return $result;
}
// 或者簡單版(非加密場景)
function randomStringSimple($length, $charset = '23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ') {
$result = '';
$clen = strlen($charset);
for ($i = 0; $i < $length; $i++) {
$result .= $charset[mt_rand(0, $clen - 1)];
}
return $result;
}
echo randomCharset(32); // 32 位安全 Token
echo randomStringSimple(6); // 6 位驗證碼(去除易混淆字符 0/O/1/l/I)
?>
<?php
$min = 1; $max = 100000; $count = 1000;
$cases = [
'shuffle' => fn() => uniqueRandShuffle($min, $max, $count),
'shuffle_opt' => fn() => uniqueRandShuffleOptimized($min, $max, $count),
'flip' => fn() => uniqueRandFlip($min, $max, $count),
'set' => fn() => uniqueRandSet($min, $max, $count),
'reservoir' => fn() => uniqueRandReservoir($min, $max, $count),
];
foreach ($cases as $name => $fn) {
$start = microtime(true);
for ($i = 0; $i < 100; $i++) $fn();
echo "$name: " . (microtime(true) - $start) . "s\n";
}
/*
典型輸出(PHP 8.2):
shuffle: 0.45s (構建 10萬元素數組開銷大)
shuffle_opt: 0.012s (部分洗牌極快)
flip: 0.008s
set: 0.006s (最快)
reservoir: 0.009s (大範圍王者,不構建池)
*/
?>
<?php
/**
* 真實業務:25 幅作品,每用戶投 16 票,不重複
* 200 用戶數據丟失,需批量補全
*/
function generateVotes($workCount = 25, $voteCount = 16, $userCount = 200) {
$votes = [];
for ($u = 0; $u < $userCount; $u++) {
// 方案:集合法(最快)
$set = [];
while (count($set) < $voteCount) {
$set[mt_rand(1, $workCount)] = true;
}
$votes[] = array_keys($set);
}
return $votes;
}
$votes = generateVotes();
// 入庫:batch insert user_votes (user_id, work_id)
foreach ($votes as $uid => $works) {
foreach ($works as $wid) {
// INSERT INTO user_votes (user_id, work_id) VALUES ($uid, $wid)
}
}
?>
random_bytes()/random_int()。shuffle() 或 array_values() 重置**。range() 只支持整數,浮點數需自行映射。**小範圍首選 set 法(鍵名去重最快)**,**大範圍/受限內存唯一選 Reservoir Sampling**,**密碼學場景用 random_bytes**。理解 Fisher-Yates 洗牌、Reservoir Sampling、哈希去重三大核心算法,靈活組合應對所有不重複隨機需求。封裝為 RandomHelper::uniqueInts() 統一項目口徑,拒絕重複造輪子。