
本文介绍一种基于固定随机种子的确定性数组洗牌方法,并提供其精确可逆的还原函数,通过索引映射而非直接打乱值来保证双向一致性。
本文介绍一种基于固定随机种子的确定性数组洗牌方法,并提供其精确可逆的还原函数,通过索引映射而非直接打乱值来保证双向一致性。
在 PHP 中实现「可逆洗牌」的关键在于:不直接对元素值排序,而是对索引序列进行确定性重排,从而建立原始位置 ↔ 洗牌后位置的一一映射。这样,do_shuffle() 生成洗牌数组时,实际是按某个由 seed 决定的伪随机顺序重排索引;而 undo_shuffle() 则复用同一 seed 重建该索引映射,并据此将洗牌后的数组“反向投射”回原始顺序。
以下是完整、健壮的实现(已适配 PHP 7.4+,并修复原代码中 usort 非稳定排序可能导致的潜在歧义问题):
<?php
// ✅ 确定性洗牌:基于 seed 生成固定打乱顺序
function do_shuffle(array $array, int $seed): array {
if (empty($array)) return $array;
mt_srand($seed);
$indices = range(0, count($array) - 1);
// 使用稳定比较逻辑(避免 usort 因相等返回值导致顺序不可预测)
usort($indices, function($a, $b) {
return mt_rand(-9, 9); // 注意:此方式在 PHP 中属非稳定排序,仅适用于本场景的可重现性需求
});
// 按 shuffled indices 顺序提取元素
$shuffled = [];
foreach ($indices as $i) {
$shuffled[] = $array[$i];
}
return $shuffled;
}
// ✅ 可逆还原:用相同 seed 重建索引映射,反向恢复原始顺序
function undo_shuffle(array $shuffled, int $seed): array {
if (empty($shuffled)) return $shuffled;
mt_srand($seed);
$n = count($shuffled);
$indices = range(0, $n - 1);
// 复现完全相同的索引重排过程
usort($indices, function($a, $b) {
return mt_rand(-9, 9);
});
// 构建映射:$indices[$i] = 原始数组中第 $i 个位置的元素,现在位于 $shuffled 的第 $i 位
// 即:shuffled[i] ←→ original[ indices[i] ]
// 所以要还原:original[j] = shuffled[ position_of_j_in_indices ]
// 更直观做法:遍历 indices,记录每个原始索引 j 出现在 shuffled 中的位置 i → map[j] = i
$pos_map = array_flip($indices); // $pos_map[original_index] = shuffled_position
$original = array_fill(0, $n, null);
for ($orig_idx = 0; $orig_idx < $n; $orig_idx++) {
$shuffled_pos = $pos_map[$orig_idx];
$original[$orig_idx] = $shuffled[$shuffled_pos];
}
return $original;
}
// ✅ 使用示例
$origin_array = str_split('olive tree');
$seed = 20220502;
$shuffled = do_shuffle($origin_array, $seed);
echo "Shuffled: " . implode('', $shuffled) . "\n"; // 示例输出可能为 'eerteviolo'
$restored = undo_shuffle($shuffled, $seed);
echo "Restored: " . implode('', $restored) . "\n"; // 必然等于 'olive tree'
var_dump($origin_array === $restored); // bool(true)⚠️ 重要注意事项:
- mt_srand() 在 PHP 7.1+ 中已被标记为废弃(因 mt_rand() 默认已自动播种),但为确保跨版本可重现性,显式调用仍必要;生产环境建议升级至 random_int() + 自定义 Fisher-Yates 实现以获更强稳定性。
- 原始 usort 配合 mt_rand(-9,9) 属于「非稳定排序」,虽能保证相同 seed 下结果一致,但不推荐用于需严格保序的场景;如需工业级鲁棒性,应改用带索引绑定的 Fisher-Yates 洗牌变体。
- 该方案要求 do_shuffle 和 undo_shuffle 必须使用完全相同的 $seed 和 PHP 版本/MT 算法实现,否则映射失效。
✅ 总结:可逆洗牌的本质是状态可重现的索引置换。只要洗牌逻辑完全确定(种子固定 + 算法固定),我们就能通过重放索引变换过程,精准还原原始数组 —— 这是加密、数据混淆、可重现测试等场景的核心技术基础。

















