Go 的 slices.Sorted 适合把一个 iter.Seq 迭代器整理成新的有序切片。它不会要求你先准备一个可修改的原始切片,调用结果也是独立的 []E,因此很适合把 map 的键、懒迭代结果转换为稳定的展示或导出顺序。
最小写法是:
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
counts := map[string]int{"pear": 2, "apple": 4, "orange": 1}
// maps.Keys 返回键迭代器,Sorted 负责收集并按字符串自然顺序排序。
keys := slices.Sorted(maps.Keys(counts))
fmt.Println(keys) // [apple orange pear]
}
这里的关键不是 map 本身变成有序 map,而是先取得键的迭代器,再构造一个有明确顺序的新切片。
先看 slices.Sorted 的输入输出
slices.Sorted 的签名可以概括为 func Sorted[E cmp.Ordered](seq iter.Seq[E]) []E。输入是只产生元素的 iter.Seq[E],元素类型必须满足 cmp.Ordered;输出是收集后排序的普通切片。
因此它解决的是“迭代器结果怎样得到排序副本”,不是“怎样让迭代器本身保持有序”。迭代器仍然只负责产生值,排序发生在结果切片上。
用 maps.Keys 得到稳定的 map 键集合
map 的遍历顺序不能作为输出顺序使用。如果要生成日志、配置摘要或接口中的稳定字段列表,可以把 maps.Keys 交给 slices.Sorted:
package main
import (
"fmt"
"maps"
"slices"
)
func sortedKeys(values map[string]int) []string {
// 只排序键,不改变原 map,也不承诺 map 后续遍历有序。
return slices.Sorted(maps.Keys(values))
}
func main() {
values := map[string]int{"z": 26, "a": 1, "m": 13}
fmt.Println(sortedKeys(values)) // [a m z]
}
这段代码的稳定性来自返回的切片,而不是来自 map。后续遍历 values 仍然不能依赖顺序;需要稳定输出时,每次都应使用排序后的键切片。
自定义 iter.Seq 时要把 yield 当作输入边界
如果数据来自自定义迭代器,直接把产生值的函数交给 slices.Sorted 即可。迭代器只需要在值可用时调用 yield,收到停止信号后结束产生:
package main
import (
"fmt"
"iter"
"slices"
)
func oddValues() iter.Seq[int] {
return func(yield func(int) bool) {
for _, value := range []int{9, 2, 7, 2, 5} {
// yield 返回 false 时,消费方不再需要后续值,应及时停止。
if !yield(value) {
return
}
}
}
}
func main() {
sorted := slices.Sorted(oddValues())
fmt.Println(sorted) // [2 2 5 7 9]
}
这里的结果包含重复的 2,因为 Sorted 只排序,不去重。若迭代器连接着文件、网络或数据库资源,资源的关闭责任仍属于迭代器或它的创建者,不能因为结果已经变成切片就忽略清理。
空输入、重复值和副本边界
可以把常见边界整理成下面这张表:
| 输入情况 | 结果关注点 | 使用建议 |
|---|---|---|
| 空迭代器 | 得到空结果切片,不会凭空产生元素 | 直接判断 len(result) |
| 存在重复值 | 重复值仍保留,只改变排列顺序 | 需要去重时另设去重步骤 |
| 字符串或整数 | 使用类型的自然比较规则 | 适合直接调用 Sorted |
| 结构体或自定义规则 | 不满足 cmp.Ordered | 改用 slices.SortedFunc |
“副本”应理解为结果容器重新收集了迭代器产生的元素。对于字符串、整数等值类型,结果元素本身就是值;如果元素是指针或包含引用字段的结构体,Sorted 只负责排列元素,不会递归复制它们指向的对象。
Sorted、SortedFunc 和原地排序怎么选
选择可以按输入形态和比较需求判断:
slices.Sorted:已有iter.Seq,元素可以使用自然顺序,想得到新的排序切片。slices.SortedFunc:仍然从迭代器得到新切片,但元素需要自定义比较规则,例如按结构体字段排序。slices.Sort或slices.SortFunc:已经有普通切片,并且允许直接修改它时使用。
不要为了“稳定输出”先把 map 转成任意切片再依赖原顺序;也不要把 slices.Sorted 当成去重函数。先确认输入是不是迭代器,再确认元素能否自然比较,最后决定是否允许修改已有切片。
小结
slices.Sorted 的核心就是“消费 iter.Seq,生成排序后的新切片”。对 map 键使用 slices.Sorted(maps.Keys(values)) 可以获得稳定输出;对重复值它只排序不去重;对非 cmp.Ordered 类型则应使用 SortedFunc。如果已有普通切片且允许修改,原地排序 API 会更直接。
相关问题
为什么 map 不能直接排序? map 没有可供排序的连续元素序列,通常要先取键或值,再对生成的切片排序。
slices.Sorted 会改变原始切片吗?它的输入是迭代器,结果是重新收集的切片;原始数据来源不会因为排序结果而被原地重排。
需要按结构体字段排序怎么办?使用 slices.SortedFunc,提供返回负数、零或正数的比较函数。

Go filepath.Rel 怎样判断两个目录是否可相对化
