Golang 实现数组/切片随机排序的两种方案

一颗松果
一颗松果
发布时间:2026-10-16| 7人浏览过
原创

在处理推荐数据等场景时,常需将获取到的数据进行随机打乱。本文介绍在 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 切片操作优化的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。