注:本文的灵感来源于GOPHER 2020年大会陈皓的分享,原PPT的链接可能并不方便获取,所以我下载了一份PDF到git仓,方便大家阅读。我将结合自己的实际项目经历,与大家一起细品这份文档。
目录
- 映射、规约与过滤
- 应用场景探索
- 泛型
Map/Reduce/Filter
代码语言:javascript复制func MapUpCase(arr []string, fn func(s string) string) []string {
var newArray = []string{}
for _, it := range arr {
newArray = append(newArray, fn(it))
}
return newArray
}
func MapLen(arr []string, fn func(s string) int) []int {
var newArray = []int{}
for _, it := range arr {
newArray = append(newArray, fn(it))
}
return newArray
}
func Reduce(arr []string, fn func(s string) int) int {
sum := 0
for _, it := range arr {
sum = fn(it)
}
return sum
}
func Filter(arr []string, fn func(n string) bool) []string {
var newArray = []string{}
for _, it := range arr {
if fn(it) {
newArray = append(newArray, it)
}
}
return newArray
}
func main() {
var list = []string{"Hao", "Chen", "MegaEase"}
// 元素一对一映射 string->string
x := MapUpCase(list, func(s string) string {
return strings.ToUpper(s)
})
fmt.Printf("%vn", x)
// [HAO CHEN MEGAEASE]
// 元素一对一映射 string->int
y := MapLen(list, func(s string) int {
return len(s)
})
fmt.Printf("%vn", y)
// [3 4 8]
// 归约:多个元素->一个元素
z := Reduce(list, func(s string) int {
return len(s)
})
fmt.Printf("%vn", z)
// 15
// 过滤:过滤不满足条件的元素
f := Filter(list, func(s string) bool {
return len(s) > 3
})
fmt.Printf("%vn", f)
// [Chen MegaEase]
}
Scenarios
Map
是一对一的场景,是 循环中对数据加工处理Reduce
是多对一,是 数据聚合处理Filter
是过滤的处理,是 数据有效性
我们以常见的账单统计相关的功能,我们会遇上大量的此类情况:
- 统计消费总额 - Reduce
- 统计用户A - Filter
- 统计本月 - Filter
- 费用转化为美金 - Map
在综合各个因素后,就是大量复杂的、管道式的Map/Reduce/Filter操作。
延伸思考一下,这块和SQL语句非常类似
Generic
耗子叔在接下来的部分,展示了用reflect
处理泛型情况。我这边简单地截取Map部分解析一下
func TransformInPlace(slice, function interface{}) interface{} {
return transform(slice, function, true)
}
// map的转换函数,slice为切片,function为对应的函数,inPlace表示是否原地处理
func transform(slice, function interface{}, inPlace bool) interface{} {
// 类型判断,必须为切片
sliceInType := reflect.ValueOf(slice)
if sliceInType.Kind() != reflect.Slice {
panic("transform: not slice")
}
// 函数的签名判断,即函数的入参必须和slice里的元素一致
fn := reflect.ValueOf(function)
elemType := sliceInType.Type().Elem()
if !verifyFuncSignature(fn, elemType, nil) {
panic("trasform: function must be of type func(" sliceInType.Type().Elem().String() ") outputElemType")
}
// 如果是原地,则直接处理函数,结果会保存到入参中(这时入参一般为指针)
// 如果非原地,那就需要新建一个切片,用来保存结果
sliceOutType := sliceInType
if !inPlace {
sliceOutType = reflect.MakeSlice(reflect.SliceOf(fn.Type().Out(0)), sliceInType.Len(), sliceInType.Len())
}
for i := 0; i < sliceInType.Len(); i {
sliceOutType.Index(i).Set(fn.Call([]reflect.Value{sliceInType.Index(i)})[0])
}
return sliceOutType.Interface()
}
func verifyFuncSignature(fn reflect.Value, types ...reflect.Type) bool {
// 类型判断
if fn.Kind() != reflect.Func {
return false
}
// 入参数量和函数签名一致,出参必须只有一个
if (fn.Type().NumIn() != len(types)-1) || (fn.Type().NumOut() != 1) {
return false
}
// 每个函数入参的类型校验
for i := 0; i < len(types)-1; i {
if fn.Type().In(i) != types[i] {
return false
}
}
// 出参类型的校验
outType := types[len(types)-1]
if outType != nil && fn.Type().Out(0) != outType {
return false
}
return true
}
仔细阅读这一块代码,我们能学到很多反射方面的知识,尤其是并不常用的函数相关的。
但是,我不建议大家在实际项目中直接使用这一块代码,毕竟其中大量的反射操作是比较耗时的,尤其是在延迟非常敏感的web服务器中。
如果我们多花点时间、直接编写指定类型的代码,那么就能在编译期发现错误,运行时也可以跳过反射的耗时。