Golang 实现数组/切片随机排序的两种方案
在处理推荐数据等场景时,常需将获取到的数据进行随机打乱。本文介绍在 Golang 中通过 `rand.Shuffle` 和手动交换索引两种方案实现切片随机排序的方法,并强调初始化随机种子的重要性。
背景与目标
在实际开发中(如电商推荐列表),通常需要从数据库获取一批数据后,在应用层将其随机打乱再返回给前端。相比于直接使用数据库的 ORDER BY RAND() 方式,在代码中进行处理通常性能更好且更可控。下面介绍两种基于 Golang 标准库的实现方式。
准备条件
实现随机排序需要引入以下标准库包:
fmt:用于打印输出math/rand:提供随机数生成及洗牌功能time:用于获取当前时间戳以初始化随机种子
import (
"fmt"
"math/rand"
"time"
)
定义数据结构
为了演示,我们定义一个模拟商品分类的结构体。在实际业务中,你可能使用的是从数据库查询出来的任意结构体或 Map。
type CategoryEntity struct {
GrouponId int64 // 团ID
MerchandiseId int64 // 商品ID
CategoryId int64 // 分类ID
CategoryTitle string // 分类名称
}
方案一:使用 rand.Shuffle(推荐)
第1步:初始化随机种子
Golang 的 math/rand 包默认种子固定,导致每次运行生成的“随机”序列相同。必须在使用前设置种子,通常使用当前时间戳。
// 注意:Go 1.20+ 版本中 rand 已自动播种,此步骤在新版本中非强制,但为兼容旧版本或明确意图建议保留
rand.Seed(time.Now().Unix())
第2步:调用 rand.Shuffle 进行重排
rand.Shuffle 是 Go 提供的内置洗牌函数,它采用 Fisher-Yates 算法变体,效率高且代码简洁。我们需要传入切片长度和一个交换元素的回调函数。
func main() {
// 模拟数据组装
data := make([]CategoryEntity, 10)
data[0] = CategoryEntity{GrouponId: 0, MerchandiseId: 1117891, CategoryId: 726, CategoryTitle: "蔬菜"}
data[1] = CategoryEntity{GrouponId: 1, MerchandiseId: 1110162, CategoryId: 1505, CategoryTitle: "调料调味"}
data[2] = CategoryEntity{GrouponId: 2, MerchandiseId: 1117822, CategoryId: 746, CategoryTitle: "水果"}
data[3] = CategoryEntity{GrouponId: 3, MerchandiseId: 1115770, CategoryId: 1408, CategoryTitle: "个人护理"}
data[4] = CategoryEntity{GrouponId: 4, MerchandiseId: 1116528, CategoryId: 732, CategoryTitle: "肉"}
data[5] = CategoryEntity{GrouponId: 5, MerchandiseId: 1116526, CategoryId: 727, CategoryTitle: "休闲食品"}
data[6] = CategoryEntity{GrouponId: 6, MerchandiseId: 1117188, CategoryId: 728, CategoryTitle: "粮油调味"}
data[7] = CategoryEntity{GrouponId: 7, MerchandiseId: 1117379, CategoryId: 726, CategoryTitle: "蔬菜"}
data[8] = CategoryEntity{GrouponId: 8, MerchandiseId: 1118166, CategoryId: 1005, CategoryTitle: "居家百货"}
data[9] = CategoryEntity{GrouponId: 9, MerchandiseId: 1117377, CategoryId: 746, CategoryTitle: "水果"}
fmt.Println("随机前:", data)
// 1. 设置随机种子
rand.Seed(time.Now().Unix())
// 2. 执行洗牌
// len(data) 决定范围,匿名函数定义如何交换元素
rand.Shuffle(len(data), func(i, j int) {
data[i], data[j] = data[j], data[i]
})
fmt.Println("随机后:", data)
}
方案二:手动循环交换(Fisher-Yates 算法实现)
如果不希望使用 rand.Shuffle,或者需要在旧版本 Go 中自定义更复杂的交换逻辑,可以手动实现 Fisher-Yates 洗牌算法。核心思想是从前往后遍历,每次将一个元素与剩余部分中的任意一个位置交换。
第1步:编写交换辅助函数
虽然 Go 支持多变量赋值直接交换,但为了逻辑清晰,可以封装一个交换函数。
// 交换切片中两个位置的元素
func exchange(data []CategoryEntity, i, j int) {
data[i], data[j] = data[j], data[i]
}
第2步:主逻辑实现
func main() {
// ... (此处省略与前例相同的 data 初始化代码) ...
fmt.Println("随机前:", data)
// 1. 设置随机种子
rand.Seed(time.Now().Unix())
length := len(data)
// 2. 遍历每一个位置
for i := 0; i < length; i++ {
// 3. 生成一个 0 到 length-1 之间的随机索引
j := rand.Intn(length)
// 4. 执行交换
exchange(data, j, i)
}
fmt.Println("随机后:", data)
}
关键注意事项
随机种子的初始化
如果忘记调用 rand.Seed(),math/rand 会使用默认种子(通常是 1),导致每次程序启动后的随机序列完全一致。这在测试时可能看不出问题,但在生产环境会导致用户体验固化。
注:自 Go 1.20 起,全局随机源会自动初始化,显式调用 rand.Seed 不再必需,但显式声明有助于代码可读性和兼容性。
安全性提示
math/rand 生成的随机数适合一般逻辑、模拟或非安全敏感场景。若涉及加密、身份验证令牌等安全场景,请务必使用 crypto/rand 包。
总结
在 Golang 中实现数组或切片的随机排序非常便捷。推荐使用内置的 rand.Shuffle 方法,因为它语义明确且经过优化。若需自定义交互逻辑,可采用手动循环交换的方式。无论哪种方案,确保正确初始化随机种子是获得真正随机结果的前提。
以上就是 Golang 数组随机排序的详细实现方法,更多关于 Golang 切片操作优化的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













