在Go语言的实际开发场景中,我们常常需要根据一个已有的参考切片的顺序,对另一个目标切片进行排序。比如参考切片是用户自定义的商品ID顺序,目标切片是对应商品的信息集合,要求目标切片的顺序和参考切片的ID顺序保持一致。

低效的实现方式及问题
很多开发者第一反应会用双层循环的方式实现,先遍历参考切片,再遍历目标切片找到对应元素放入结果切片,代码如下:
package main
import "fmt"
type Product struct {
ID int
Name string
}
// 低效实现方式
func sortByRefSliceSlow(refIDs []int, products []Product) []Product {
result := make([]Product, 0, len(products))
for _, refID := range refIDs {
for _, product := range products {
if product.ID == refID {
result = append(result, product)
break
}
}
}
return result
}
func main() {
refIDs := []int{3, 1, 2}
products := []Product{
{ID: 1, Name: "商品A"},
{ID: 2, Name: "商品B"},
{ID: 3, Name: "商品C"},
}
sorted := sortByRefSliceSlow(refIDs, products)
fmt.Println(sorted)
}
这种方式的时间复杂度是O(n*m),其中n是参考切片的长度,m是目标切片的长度,当数据量较大时,排序效率会非常低,不是正确的实现方案。
正确的高效实现思路
正确的实现方式是通过一次遍历把目标切片的元素构建成映射,键为排序依据的字段值,值为对应的元素,之后只需要遍历参考切片从映射中取元素即可,时间复杂度可以降到O(n+m)。
实现步骤
- 定义一个映射,键为参考切片的元素类型,值为目标切片的元素
- 遍历目标切片,将每个元素按照排序依据字段存入映射
- 遍历参考切片,依次从映射中取出对应元素放入结果切片
- 处理参考切片中存在但目标切片不存在的键,以及目标切片中存在但参考切片不存在的元素两种情况
完整实现代码示例
以下是通用的实现代码,支持处理参考切片和目标切片长度不一致的情况:
package main
import "fmt"
type Product struct {
ID int
Name string
}
// 正确高效实现方式
func sortByRefSlice(refIDs []int, products []Product) []Product {
// 构建ID到Product的映射
productMap := make(map[int]Product)
for _, product := range products {
productMap[product.ID] = product
}
// 按照参考切片顺序取元素
result := make([]Product, 0, len(refIDs))
for _, refID := range refIDs {
if product, ok := productMap[refID]; ok {
result = append(result, product)
// 可选:删除已匹配的元素,方便后续处理剩余元素
delete(productMap, refID)
}
}
// 如果需要把目标切片中剩余的元素也追加到结果后面,可以加这段逻辑
// for _, product := range productMap {
// result = append(result, product)
// }
return result
}
func main() {
refIDs := []int{3, 1, 2}
products := []Product{
{ID: 1, Name: "商品A"},
{ID: 2, Name: "商品B"},
{ID: 3, Name: "商品C"},
}
sorted := sortByRefSlice(refIDs, products)
fmt.Println(sorted)
// 测试参考切片有额外ID的情况
refIDs2 := []int{3, 4, 1, 2}
sorted2 := sortByRefSlice(refIDs2, products)
fmt.Println(sorted2)
}
注意事项
- 如果目标切片中存在重复的排序依据字段值,映射只会保留最后一个元素,这种情况需要根据业务需求调整映射结构,比如值改为切片存储所有重复元素
- 如果参考切片中的排序依据字段值有重复,结果切片也会保留重复顺序,符合参考切片的重复规则
- 如果不需要保留目标切片中不在参考切片里的元素,可以不用处理映射中剩余的元素,反之则可以根据业务需求决定剩余元素的追加顺序
总结
Go中按另一切片排序切片的核心是通过映射降低查找的时间复杂度,避免嵌套循环带来的性能问题。这种实现方式逻辑清晰,效率更高,适合各种数据量下的排序场景,开发者可以根据具体的业务需求调整细节处理逻辑。