shuffle() 原地打亂陣列順序並**重置數字鍵 (0...n-1)**,關聯鍵名會**遺失**。本文說明此行為、保留鍵名的替代方案、array_rand() 隨機取鍵用法、以及百萬級陣列抽樣的記憶體優化策略。
<?php
$data = [
['name' => 'A', 'rank' => 40],
['name' => 'B', 'rank' => 50],
['name' => 'C', 'rank' => 60],
['name' => 'D', 'rank' => 50],
];
shuffle($data);
// $data 現在順序隨機,但鍵已重置為 0,1,2,3
// 取前 2 筆
$sample = array_slice($data, 0, 2);
?>
關鍵點:
bool (成功/失敗),**陣列原地修改**。['a'=>1, 'b'=>2] → [0=>1, 1=>2]。mt_rand),速度快、分布均勻。<?php
function shuffle_assoc(array &$array): void {
$keys = array_keys($array);
shuffle($keys);
$result = [];
foreach ($keys as $key) {
$result[$key] = $array[$key];
}
$array = $result;
}
// 測試
$assoc = ['id1' => 'A', 'id2' => 'B', 'id3' => 'C'];
shuffle_assoc($assoc);
// 鍵名 id1/id2/id3 保留,順序隨機
?>
或用 uksort($arr, fn() => mt_rand(-1, 1)) (但分布不如 shuffle 均勻)。
<?php $arr = ['a' => 10, 'b' => 20, 'c' => 30, 'd' => 40]; $keys = array_rand($arr, 2); // 取 2 個鍵,回傳陣列 // $keys 如 ['c', 'a'] $sample = array_intersect_key($arr, array_flip($keys)); // $sample = ['c' => 30, 'a' => 10] ?>
$num:取鍵數量;省略時回傳單一鍵 (字串)。問題:shuffle($millionItems) 需載入整個陣列記憶體,且 O(n) 打亂耗時。
方案 A:資料庫層面抽樣 (最推薦)
-- MySQL: 高效隨機取樣 (避免 ORDER BY RAND()) SELECT * FROM `table` WHERE id >= (SELECT FLOOR(RAND() * (SELECT MAX(id) FROM `table`))) ORDER BY id LIMIT 10;
方案 B:水塘抽樣 (單次遍歷、常數記憶體)
<?php
function reservoirSample(iterable $source, int $k): array {
$reservoir = [];
$i = 0;
foreach ($source as $item) {
if ($i < $k) {
$reservoir[] = $item;
} else {
$j = mt_rand(0, $i);
if ($j < $k) $reservoir[$j] = $item;
}
$i++;
}
return $reservoir;
}
// 使用:直接遍歷 Generator/資料庫游標,記憶體 O(k)
$sample = reservoirSample($db->createCommand('SELECT * FROM big_table')->query(), 100);
?>
方案 C:系統隨機 ID 範圍 (需 ID 連續/近似均勻)
$ids = range(1, $maxId); shuffle($ids); $sampleIds = array_slice($ids, 0, 100); // 再 WHERE id IN (...) 查詢
| 函數 | 修改原陣列 | 保留關聯鍵 | 用途 | 記憶體 |
|---|---|---|---|---|
| shuffle | 是 | 否 (重置) | 打亂順序、取前 N | O(n) |
| shuffle_assoc | 是 | 是 | 打亂順序、保鍵 | O(n) |
| array_rand | 否 | 是 (回傳鍵) | 隨機取鍵/子集 | O(n) |
| reservoirSample | 否 | 視來源 | 海量/串流抽樣 | O(k) |