
本文介绍一种基于固定种子的可逆数组洗牌方法:通过打乱索引而非元素本身,构建原始位置与洗牌后位置的映射关系,从而在已知种子时精确还原原始数组。
本文介绍一种基于固定种子的可逆数组洗牌方法:通过打乱索引而非元素本身,构建原始位置与洗牌后位置的映射关系,从而在已知种子时精确还原原始数组。
在 PHP 中,usort() 配合 mt_rand() 实现的随机排序本质上是伪随机且依赖种子状态的——只要重置相同的 mt_srand($seed),后续生成的随机序列就完全一致。但直接对原数组调用 usort() 会破坏元素与索引的对应关系,导致无法逆向推导。因此,核心思路是:不 shuffle 值,而是 shuffle 索引,从而获得一个确定性的位置映射表。
✅ 正确的可逆洗牌实现
首先,do_shuffle() 函数需稍作优化以确保行为稳定(避免闭包中隐式依赖外部状态):
function do_shuffle(array $array, int $seed): array {
mt_srand($seed);
$indices = range(0, count($array) - 1);
usort($indices, function() { return mt_rand(-9, 9); });
// 按打乱后的索引顺序重排原数组
return array_map(fn($i) => $array[$i], $indices);
}而 undo_shuffle() 的关键在于:复现完全相同的索引打乱过程,然后将洗牌后的数组按该映射“反向填充”回原始索引位置:
function undo_shuffle(array $shuffled, int $seed): array {
mt_srand($seed);
$n = count($shuffled);
$indices = range(0, $n - 1);
usort($indices, function() { return mt_rand(-9, 9); });
// $indices[i] 表示:洗牌后第 i 个位置的元素,原本在 $indices[i] 这个索引上
// 所以我们要把 $shuffled[i] 放回 $indices[i] 对应的位置
$restored = array_fill(0, $n, null);
foreach ($indices as $shuffled_pos => $original_pos) {
$restored[$original_pos] = $shuffled[$shuffled_pos];
}
// 保证键为连续整数(等价于 array_values),但 ksort 更稳妥(兼容稀疏键)
ksort($restored);
return array_values($restored);
}⚠️ 注意事项
- mt_srand() 必须在每次调用前重置:PHP 的 mt_rand() 是全局状态,不同函数间会相互干扰;未重置将导致映射不一致。
- usort 的比较函数必须严格返回整数:mt_rand(-9, 9) 是安全的,但若使用浮点或布尔值可能引发不可预测排序(PHP 8+ 对此更严格)。
- 数组键会被重置:本方案默认处理数值索引数组;若需保留关联键,需额外记录并映射(此时建议改用 array_keys() + 索引映射)。
- 非加密安全:该方法适用于确定性重现场景(如 UI 排序、测试数据生成),不适用于密码学或安全敏感用途。
✅ 完整验证示例
$origin = str_split('olive tree');
$seed = 20220502;
$shuffled = do_shuffle($origin, $seed);
$restored = undo_shuffle($shuffled, $seed);
var_dump($origin === $restored); // bool(true)只要种子一致、随机数生成器状态重置正确、索引映射逻辑对称,即可实现 100% 精确还原。这种“索引置换法”是实现确定性可逆洗牌的通用范式,也广泛应用于游戏开发、区块链随机抽样及算法可重现性保障中。

















