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

简介: 但是,我不建议大家在实际项目中直接使用这一块代码,毕竟其中大量的反射操作是比较耗时的,尤其是在延迟非常敏感的web服务器中。如果我们多花点时间、直接编写指定类型的代码,那么就能在编译期发现错误,运行时也可以跳过反射的耗时。

目录

Map/Reduce/Filter

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部分解析一下

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服务器中。

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

目录
相关文章
|
28天前
|
Go
go语言中遍历映射(map)
go语言中遍历映射(map)
42 8
|
18天前
|
存储 Go
go语言中映射
go语言中映射
32 11
|
20天前
|
Go
go语言for遍历映射(map)
go语言for遍历映射(map)
30 12
|
27天前
|
Go
go语言中遍历映射同时遍历键和值
go语言中遍历映射同时遍历键和值
26 7
|
24天前
|
存储 Go
go语言 遍历映射(map)
go语言 遍历映射(map)
33 2
|
27天前
|
存储 Go
go语言中遍历映射遍历值
go语言中遍历映射遍历值
20 1
|
27天前
|
Go
go语言中遍历映射遍历键
go语言中遍历映射遍历键
19 1
|
1月前
|
存储 Go
|
3月前
|
存储 Go 索引
Go to Learn Go之映射
Go to Learn Go之映射
42 7
|
4月前
|
Go
Go 1.21的新特性: 切片和映射
Go 1.21的新特性: 切片和映射