前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go编程模式 - 6-映射、归约与过滤

Go编程模式 - 6-映射、归约与过滤

作者头像
junedayday
发布2021-08-05 11:20:11
2590
发布2021-08-05 11:20:11
举报
文章被收录于专栏:Go编程点滴

注:本文的灵感来源于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("%v\n", x)
 // [HAO CHEN MEGAEASE]

 // 元素一对一映射 string->int
 y := MapLen(list, func(s string) int {
  return len(s)
 })
 fmt.Printf("%v\n", y)
 // [3 4 8]

 // 归约:多个元素->一个元素
 z := Reduce(list, func(s string) int {
  return len(s)
 })
 fmt.Printf("%v\n", z)
 // 15

 // 过滤:过滤不满足条件的元素
 f := Filter(list, func(s string) bool {
  return len(s) > 3
 })
 fmt.Printf("%v\n", f)
 // [Chen MegaEase]
}

Scenarios

  • Map 是一对一的场景,是 循环中对数据加工处理
  • Reduce 是多对一,是 数据聚合处理
  • Filter是过滤的处理,是 数据有效性

我们以常见的账单统计相关的功能,我们会遇上大量的此类情况:

  1. 统计消费总额 - Reduce
  2. 统计用户A - Filter
  3. 统计本月 - Filter
  4. 费用转化为美金 - Map

在综合各个因素后,就是大量复杂的、管道式的Map/Reduce/Filter操作。

延伸思考一下,这块和SQL语句非常类似

Generic

耗子叔在接下来的部分,展示了用reflect处理泛型情况。我这边简单地截取Map部分解析一下

代码语言:javascript
复制
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服务器中。

如果我们多花点时间、直接编写指定类型的代码,那么就能在编译期发现错误,运行时也可以跳过反射的耗时。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go编程点滴 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • Map/Reduce/Filter
  • Scenarios
  • Generic
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档