Go语言的1.17版本发布了,其中开始正式支持泛型了。虽然还有一些限制(比如,不能把泛型函数export),但是,可以体验了。我的这个《Go编程模式》的系列终于有了真正的泛型编程了,再也不需要使用反射或是go generation这些难用的技术了。周末的时候,我把Go 1.17下载下来,然后,体验了一下泛型编程,还是很不错的。下面,就让我们来看一下Go的泛型编程。(注:不过,如果你对泛型编程的重要性还不是很了解的话,你可以先看一下之前的这篇文章《Go编程模式:Go Generation》,然后再读一下《Go编程模式:MapReduce》)

本文是全系列中第10 / 10篇:Go编程模式

« 上一篇文章

初探

我们先来看一个简单的示例:

  1. package main
  2. import "fmt"
  3. func printAny[T any] (arr []T) {
  4. for _, v := range arr {
  5. fmt.Print(v)
  6. fmt.Print(" ")
  7. }
  8. fmt.Println("")
  9. }
  10. func main() {
  11. strs := []string{"Hello", "World", "Generics"}
  12. decs := []float64{3.14, 1.14, 1.618, 2.718 }
  13. nums := []int{2,4,6,8}
  14. printAny(strs)
  15. printAny(decs)
  16. printAny(nums)
  17. }

run build 的命令执行时指定 -G 标识或在 https://go2goplay.golang.org/ 运行如下代码

  1. PS D:\Projects\Github\NoobWu\go-samples\simple> go run -gcflags=-G=3 .\generic.go
  2. Hello World Generics
  3. 3.14 1.14 1.618 2.718
  4. 2 4 6 8
  5. PS D:\Projects\Github\NoobWu\go-samples\simple>

image.png
上面这个例子中,有一个 print() 函数,这个函数就是想输出数组的值,如果没有泛型的话,这个函数需要写出 int 版,float版,string 版,以及我们的自定义类型(struct)的版本。现在好了,有了泛型的支持后,我们可以使用 [T any] 这样的方式来声明一个泛型类型(有点像 C++ 的 typename T),然后面都使用 T 来声明变量就好。

上面这个示例中,我们泛型的 print() 支持了三种类型的适配—— int型,float64型,和 string型。要让这段程序跑起来需要在编译行上加上 -gcflags=-G=3 编译参数(这个编译参数会在 1.18 版上成为默认参数),如下所示:

  1. $ go run -gcflags=-G=3 ./main.go

有了个操作以后,我们就可以写一些标准的算法了,比如,一个查找的算法

  1. func find[T comparable] (arr []T, elem T) int {
  2. for i, v := range arr {
  3. if v == elem {
  4. return i
  5. }
  6. }
  7. return -1
  8. }

我们注意到,我们没有使用 [T any]的形式,而是使用 [T comparable]的形式,comparable是一个接口类型,其约束了我们的类型需要支持 == 的操作, 不然就会有类型不对的编译错误。上面的这个 find() 函数同样可以使用于 int, float64或是string类型。

从上面的这两个小程序来看,Go语言的泛型已基本可用了,只不过,还有三个问题:

  • 一个是 fmt.Printf()中的泛型类型是 %v 还不够好,不能像 c++ iostream重载 >> 来获得程序自定义的输出。
  • 另外一个是,go 不支持操作符重载,所以,你也很难在泛型算法中使用“泛型操作符”如:== 等
  • 最后一个是,上面的 find() 算法依赖于“数组”,对于hash-table、tree、graph、link等数据结构还要重写。也就是说,没有一个像C++ STL那样的一个泛型迭代器(这其中的一部分工作当然也需要通过重载操作符(如:++ 来实现)

不过,这个已经很好了,让我们来看一下,可以干哪些事了。

数据结构

Stack 栈

编程支持泛型最大的优势就是可以实现类型无关的数据结构了。下面,我们用 Slices 这个结构体来实现一个Stack 的数结构。

首先,我们可以定义一个泛型的 Stack

  1. type stack [T any][]T

看上去很简单,还是 [T any] ,然后 []T 就是一个数组,接下来就是实现这个数据结构的各种方法了。下面的代码实现了 push() ,pop(),top(),len(),print()这几个方法,这几个方法和 C++的 STL 中的 Stack 很类似。(注:目前 Go 的泛型函数不支持 export,所以只能使用第一个字符是小写的函数名)

  1. func (s *stack[T]) push(elem T) {
  2. *s = append(*s, elem)
  3. }
  4. func (s *stack[T]) pop() {
  5. if len(*s) > 0 {
  6. *s = (*s)[:len(*s)-1]
  7. }
  8. }
  9. func (s *stack[T]) top() *T {
  10. if len(*s) > 0 {
  11. return &(*s)[len(*s)-1]
  12. }
  13. return nil
  14. }
  15. func (s *stack[T]) len() int {
  16. return len(*s)
  17. }
  18. func (s *stack[T]) print() {
  19. for _, elem := range *s {
  20. fmt.Print(elem)
  21. fmt.Print(" ")
  22. }
  23. fmt.Println("")
  24. }

上面的这个例子还是比较简单的,不过在实现的过程中,对于一个如果栈为空,那么 top()要么返回error要么返回空值,在这个地方卡了一下。因为,之前,我们返回的“空”值,要么是 int 的0,要么是 string 的 “”,然而在泛型的T下,这个值就不容易搞了。也就是说,除了类型泛型后,还需要有一些“值的泛型”(注:在 C++ 中,如果你要用一个空栈进行 top() 操作,你会得到一个 segmentation fault),所以,这里我们返回的是一个指针,这样可以判断一下指针是否为空。

下面是如何使用这个 stack 的代码。

  1. package main
  2. import "fmt"
  3. func print[T any](arr []T) {
  4. for _, v := range arr {
  5. fmt.Print(v)
  6. fmt.Print(" ")
  7. }
  8. fmt.Println("")
  9. }
  10. func printTest() {
  11. strs := []string{"Hello", "World", "Generics"}
  12. decs := []float64{3.14, 1.14, 1.618, 2.718}
  13. nums := []int{2, 4, 6, 8}
  14. print(strs)
  15. print(decs)
  16. print(nums)
  17. }
  18. type stack [T any][]T
  19. func (s *stack[T]) push(elem T) {
  20. *s = append(*s, elem)
  21. }
  22. func (s *stack[T]) pop() {
  23. if len(*s) > 0 {
  24. *s = (*s)[:len(*s)-1]
  25. }
  26. }
  27. func (s *stack[T]) top() *T {
  28. if len(*s) > 0 {
  29. return &(*s)[len(*s)-1]
  30. }
  31. return nil
  32. }
  33. func (s *stack[T]) len() int {
  34. return len(*s)
  35. }
  36. func (s *stack[T]) print() {
  37. for _, elem := range *s {
  38. fmt.Print(elem)
  39. fmt.Print(" ")
  40. }
  41. fmt.Println("")
  42. }
  43. func stackTest() {
  44. ss := stack[string]{}
  45. ss.push("Hello")
  46. ss.push("Hao")
  47. ss.push("Chen")
  48. ss.print()
  49. fmt.Printf("stack top is - %v\n", *(ss.top()))
  50. ss.pop()
  51. ss.pop()
  52. ss.print()
  53. ns := stack[int]{}
  54. ns.push(10)
  55. ns.push(20)
  56. ns.print()
  57. ns.pop()
  58. ns.print()
  59. *ns.top() += 1
  60. ns.print()
  61. ns.pop()
  62. fmt.Printf("stack top is - %v\n", ns.top())
  63. }
  64. func main() {
  65. stackTest()
  66. }
  1. PS D:\Projects\Github\NoobWu\go-samples\simple> go run -gcflags=-G=3 .\generic.go

image.png

LinkList 双向链表

下面我们再来看一个双向链表的实现。下面这个实现中实现了 这几个方法:

  • add() – 从头插入一个数据结点
  • push() – 从尾插入一个数据结点
  • del() – 删除一个结点(因为需要比较,所以使用了 compareable 的泛型)
  • print() – 从头遍历一个链表,并输出值。 ```go type node[T comparable] struct { data T prev node[T] next node[T] }

type list[T comparable] struct { head, tail *node[T] len int }

func (l *list[T]) isEmpty() bool { return l.head == nil && l.tail == nil }

func (l *list[T]) add(data T) { n := &node[T] { data : data, prev : nil, next : l.head, } if l.isEmpty() { l.head = n l.tail = n } l.head.prev = n l.head = n }

func (l *list[T]) push(data T) { n := &node[T] { data : data, prev : l.tail, next : nil, } if l.isEmpty() { l.head = n l.tail = n } l.tail.next = n l.tail = n }

func (l *list[T]) del(data T) { for p := l.head; p != nil; p = p.next { if data == p.data {

  1. if p == l.head {
  2. l.head = p.next
  3. }
  4. if p == l.tail {
  5. l.tail = p.prev
  6. }
  7. if p.prev != nil {
  8. p.prev.next = p.next
  9. }
  10. if p.next != nil {
  11. p.next.prev = p.prev
  12. }
  13. return
  14. }

} }

func (l *list[T]) print() { if l.isEmpty() { fmt.Println(“the link list is empty.”) return } for p := l.head; p != nil; p = p.next { fmt.Printf(“[%v] -> “, p.data) } fmt.Println(“nil”) }

  1. 上面这个代码都是一些比较常规的链表操作,学过链表数据结构的同学应该都不陌生,使用的代码也不难,如下所示,都很简单,看代码就好了。
  2. ```go
  3. func main(){
  4. var l = list[int]{}
  5. l.add(1)
  6. l.add(2)
  7. l.push(3)
  8. l.push(4)
  9. l.add(5)
  10. l.print() //[5] -> [2] -> [1] -> [3] -> [4] -> nil
  11. l.del(5)
  12. l.del(1)
  13. l.del(4)
  14. l.print() //[2] -> [3] -> nil
  15. }
  1. PS D:\Projects\Github\NoobWu\go-samples\simple> go run -gcflags=-G=3 .\generic.go
  2. [5] -> [2] -> [1] -> [3] -> [4] -> nil
  3. [2] -> [3] -> nil
  4. PS D:\Projects\Github\NoobWu\go-samples\simple>

函数式范型

接下来,我们就要来看一下我们函数式编程的三大件 map() 、 reduce() 和 filter() 在之前的《Go编程模式:Map-Reduce》文章中,我们可以看到要实现这样的泛型,需要用到反射,代码复杂到完全读不懂。下面来看一下真正的泛型版本。

泛型Map

  1. func gMap[T1 any, T2 any](arr []T1, f func(T1) T2)[]T2 {
  2. result := make([]T2, len(arr))
  3. for i, elem := range arr {
  4. result[i] = f(elem)
  5. }
  6. return result
  7. }

在上面的这个 map函数中我使用了两个类型 – T1 和 T2 ,

  • T1 – 是需要处理数据的类型
  • T2 – 是处理后的数据类型

T1 和 T2 可以一样,也可以不一样。
我们还有一个函数参数 – func(T1) T2 意味着,进入的是 T1 类型的,出来的是 T2 类型的。
然后,整个函数返回的是一个 []T2
好的,我们来看一下怎么使用这个map函数:

  1. func gMapTest() {
  2. nums := []int {0,1,2,3,4,5,6,7,8,9}
  3. squares := gMap(nums, func (elem int) int {
  4. return elem * elem
  5. })
  6. print(squares) //0 1 4 9 16 25 36 49 64 81
  7. strs := []string{"Hao", "Chen", "MegaEase"}
  8. upstrs := gMap(strs, func(s string) string {
  9. return strings.ToUpper(s)
  10. })
  11. print(upstrs) // HAO CHEN MEGAEASE
  12. dict := []string{"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}
  13. strs = gMap(nums, func (elem int) string {
  14. return dict[elem]
  15. })
  16. print(strs) // 零 壹 贰 叁 肆 伍 陆 柒 捌 玖
  17. }
  18. func main() {
  19. // stackTest() //stack 栈
  20. //listTest() //LinkList 双链表
  21. gMapTest() //map
  22. }
  1. PS D:\Projects\Github\NoobWu\go-samples\simple> go run -gcflags=-G=3 .\generic.go
  2. 0 1 4 9 16 25 36 49 64 81
  3. HAO CHEN MEGAEASE
  4. PS D:\Projects\Github\NoobWu\go-samples\simple>

泛型 Reduce

接下来,我们再来看一下我们的 Reduce 函数,reduce 函数是把一堆数据合成一个。

  1. func gReduce[T1 any, T2 any](arr []T1, init T2, f func(T2, T1) T2) T2 {
  2. result := init
  3. for _, elem := range arr {
  4. result = f(result, elem)
  5. }
  6. return result
  7. }

函数实现起来很简单,但是感觉不是很优雅。

  • 也是有两个类型 T1 和 T2,前者是输出数据的类型,后者是佃出数据的类型。
  • 因为要合成一个数据,所以需要有这个数据的初始值 init,是 T2 类型
  • 而自定义函数 func(T2, T1) T2,会把这个 init 值传给用户,然后用户处理完后再返回出来。

下面是一个使用上的示例——求一个数组的和

  1. func gReduceTest() {
  2. nums := []int {0,1,2,3,4,5,6,7,8,9}
  3. sum := gReduce(nums, 0, func (result, elem int) int {
  4. return result + elem
  5. })
  6. fmt.Printf("Sum = %d \n", sum)
  7. }
  8. func main() {
  9. // stackTest() //stack 栈
  10. //listTest() //LinkList 双链表
  11. //gMapTest() //map
  12. gReduceTest() //reduce
  13. }
  1. PS D:\Projects\Github\NoobWu\go-samples\simple> go run -gcflags=-G=3 .\generic.go
  2. Sum = 45
  3. PS D:\Projects\Github\NoobWu\go-samples\simple>

泛型 filter

filter 函数主要是用来做过滤的,把数据中一些符合条件(filter in)或是不符合条件(filter out)的数据过滤出来,下面是相关的代码示例

  1. func gFilter[T any] (arr []T, in bool, f func(T) bool) []T {
  2. result := []T{}
  3. for _, elem := range arr {
  4. choose := f(elem)
  5. if (in && choose) || (!in && !choose) {
  6. result = append(result, elem)
  7. }
  8. }
  9. return result
  10. }
  11. func gFilterIn[T any] (arr []T, f func(T) bool) []T {
  12. return gFilter(arr, true, f)
  13. }
  14. func gFilterOut[T any] (arr []T, f func(T) bool) []T {
  15. return gFilter(arr, false, f)
  16. }

其中,用户需要提从一个 bool 的函数,我们会把数据传给用户,然后用户只需要告诉我行还是不行,于是我们就会返回一个过滤好的数组给用户。

比如,我们想把数组中所有的奇数过滤出来

  1. nums := []int {0,1,2,3,4,5,6,7,8,9}
  2. odds := gFilterIn(nums, func (elem int) bool {
  3. return elem % 2 == 1
  4. })
  5. print(odds)
  1. PS D:\Projects\Github\NoobWu\go-samples\simple> go run -gcflags=-G=3 .\generic.go
  2. 1 3 5 7 9
  3. PS D:\Projects\Github\NoobWu\go-samples\simple>

业务示例

正如《Go编程模式:Map-Reduce》中的那个业务示例,我们在这里再做一遍。

首先,我们先声明一个员工对象和相关的数据

  1. type Employee struct {
  2. Name string
  3. Age int
  4. Vacation int
  5. Salary float32
  6. }
  7. var employees = []Employee{
  8. {"Hao", 44, 0, 8000.5},
  9. {"Bob", 34, 10, 5000.5},
  10. {"Alice", 23, 5, 9000.0},
  11. {"Jack", 26, 0, 4000.0},
  12. {"Tom", 48, 9, 7500.75},
  13. {"Marry", 29, 0, 6000.0},
  14. {"Mike", 32, 8, 4000.3},
  15. }

然后,我们想统一下所有员工的薪水,我们就可以使用前面的 reduce 函数

  1. total_pay := gReduce(employees, 0.0, func(result float32, e Employee) float32 {
  2. return result + e.Salary
  3. })
  4. fmt.Printf("Total Salary: %0.2f\n", total_pay) // Total Salary: 43502.05

我们函数这个 gReduce 函数有点啰嗦,还需要传一个初始值,在用户自己的函数中,还要关心 result 我们还是来定义一个更好的版本。

一般来说,我们用 reduce 函数大多时候基本上是统计求和或是数个数,所以,是不是我们可以定义的更为直接一些?比如下面的这个 CountIf(),就比上面的 Reduce 干净了很多。

  1. func gCountIf[T any](arr []T, f func(T) bool) int {
  2. cnt := 0
  3. for _, elem := range arr {
  4. if f(elem) {
  5. cnt += 1
  6. }
  7. }
  8. return cnt;
  9. }

我们做求和,我们也可以写一个 Sum 的泛型。

  • 处理 T 类型的数据,返回 U类型的结果
  • 然后,用户只需要给我一个需要统计的 T 的 U 类型的数据就可以了。

如下所示:

  1. type Sumable interface {
  2. type int, int8, int16, int32, int64,
  3. uint, uint8, uint16, uint32, uint64,
  4. float32, float64
  5. }
  6. func gSum[T any, U Sumable](arr []T, f func(T) U) U {
  7. var sum U
  8. for _, elem := range arr {
  9. sum += f(elem)
  10. }
  11. return sum
  12. }

上面的代码我们动用了一个叫 Sumable 的接口,其限定了 U 类型,只能是 Sumable 里的那些类型,也就是整型或浮点型,这个支持可以让我们的泛型代码更健壮一些。

于是,我们就可以完成下面的事了。

  • 1)统计年龄大于40岁的员工数

    1. old := gCountIf(employees, func (e Employee) bool {
    2. return e.Age > 40
    3. })
    4. fmt.Printf("old people(>40): %d\n", old)
    5. // ld people(>40): 2
  • 2)统计薪水超过 6000元的员工数

    1. high_pay := gCountIf(employees, func(e Employee) bool {
    2. return e.Salary >= 6000
    3. })
    4. fmt.Printf("High Salary people(>6k): %d\n", high_pay)
    5. //High Salary people(>6k): 4
  • 3)统计年龄小于30岁的员工的薪水

    1. younger_pay := gSum(employees, func(e Employee) float32 {
    2. if e.Age < 30 {
    3. return e.Salary
    4. }
    5. return 0
    6. })
    7. fmt.Printf("Total Salary of Young People: %0.2f\n", younger_pay)
    8. //Total Salary of Young People: 19000.00
  • 4)统计全员的休假天数

    1. total_vacation := gSum(employees, func(e Employee) int {
    2. return e.Vacation
    3. })
    4. fmt.Printf("Total Vacation: %d day(s)\n", total_vacation)
    5. //Total Vacation: 32 day(s)
  • 5)把没有休假的员工过滤出来

    1. no_vacation := gFilterIn(employees, func(e Employee) bool {
    2. return e.Vacation == 0
    3. })
    4. print(no_vacation)
    5. //{Hao 44 0 8000.5} {Jack 26 0 4000} {Marry 29 0 6000}

    怎么样,你大概了解了泛型编程的意义了吧。
    (全文完)

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func print[T any](arr []T) {
  7. for _, v := range arr {
  8. fmt.Print(v)
  9. fmt.Print(" ")
  10. }
  11. fmt.Println("")
  12. }
  13. func printTest() {
  14. strs := []string{"Hello", "World", "Generics"}
  15. decs := []float64{3.14, 1.14, 1.618, 2.718}
  16. nums := []int{2, 4, 6, 8}
  17. print(strs)
  18. print(decs)
  19. print(nums)
  20. }
  21. type stack[T any] []T
  22. func (s *stack[T]) push(elem T) {
  23. *s = append(*s, elem)
  24. }
  25. func (s *stack[T]) pop() {
  26. if len(*s) > 0 {
  27. *s = (*s)[:len(*s)-1]
  28. }
  29. }
  30. func (s *stack[T]) top() *T {
  31. if len(*s) > 0 {
  32. return &(*s)[len(*s)-1]
  33. }
  34. return nil
  35. }
  36. func (s *stack[T]) len() int {
  37. return len(*s)
  38. }
  39. func (s *stack[T]) print() {
  40. for _, elem := range *s {
  41. fmt.Print(elem)
  42. fmt.Print(" ")
  43. }
  44. fmt.Println("")
  45. }
  46. func stackTest() {
  47. ss := stack[string]{}
  48. ss.push("Hello")
  49. ss.push("Hao")
  50. ss.push("Chen")
  51. ss.print()
  52. fmt.Printf("stack top is - %v\n", *(ss.top()))
  53. ss.pop()
  54. ss.pop()
  55. ss.print()
  56. ns := stack[int]{}
  57. ns.push(10)
  58. ns.push(20)
  59. ns.print()
  60. ns.pop()
  61. ns.print()
  62. *ns.top() += 1
  63. ns.print()
  64. ns.pop()
  65. fmt.Printf("stack top is - %v\n", ns.top())
  66. }
  67. type node[T comparable] struct {
  68. data T
  69. prev *node[T]
  70. next *node[T]
  71. }
  72. type list[T comparable] struct {
  73. head, tail *node[T]
  74. len int
  75. }
  76. func (l *list[T]) isEmpty() bool {
  77. return l.head == nil && l.tail == nil
  78. }
  79. func (l *list[T]) add(data T) {
  80. n := &node[T]{
  81. data: data,
  82. prev: nil,
  83. next: l.head,
  84. }
  85. if l.isEmpty() {
  86. l.head = n
  87. l.tail = n
  88. }
  89. l.head.prev = n
  90. l.head = n
  91. }
  92. func (l *list[T]) push(data T) {
  93. n := &node[T]{
  94. data: data,
  95. prev: l.tail,
  96. next: nil,
  97. }
  98. if l.isEmpty() {
  99. l.head = n
  100. l.tail = n
  101. }
  102. l.tail.next = n
  103. l.tail = n
  104. }
  105. func (l *list[T]) del(data T) {
  106. for p := l.head; p != nil; p = p.next {
  107. if data == p.data {
  108. if p == l.head {
  109. l.head = p.next
  110. }
  111. if p == l.tail {
  112. l.tail = p.prev
  113. }
  114. if p.prev != nil {
  115. p.prev.next = p.next
  116. }
  117. if p.next != nil {
  118. p.next.prev = p.prev
  119. }
  120. return
  121. }
  122. }
  123. }
  124. func (l *list[T]) print() {
  125. if l.isEmpty() {
  126. fmt.Println("the link list is empty.")
  127. return
  128. }
  129. for p := l.head; p != nil; p = p.next {
  130. fmt.Printf("[%v] -> ", p.data)
  131. }
  132. fmt.Println("nil")
  133. }
  134. func listTest() {
  135. var l = list[int]{}
  136. l.add(1)
  137. l.add(2)
  138. l.push(3)
  139. l.push(4)
  140. l.add(5)
  141. l.print() //[5] -> [2] -> [1] -> [3] -> [4] -> nil
  142. l.del(5)
  143. l.del(1)
  144. l.del(4)
  145. l.print() //[2] -> [3] -> nil
  146. }
  147. func gMap[T1 any, T2 any](arr []T1, f func(T1) T2) []T2 {
  148. result := make([]T2, len(arr))
  149. for i, elem := range arr {
  150. result[i] = f(elem)
  151. }
  152. return result
  153. }
  154. func gMapTest() {
  155. nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  156. squares := gMap(nums, func(elem int) int {
  157. return elem * elem
  158. })
  159. print(squares) //0 1 4 9 16 25 36 49 64 81
  160. strs := []string{"Hao", "Chen", "MegaEase"}
  161. upstrs := gMap(strs, func(s string) string {
  162. return strings.ToUpper(s)
  163. })
  164. print(upstrs) // HAO CHEN MEGAEASE
  165. dict := []string{"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}
  166. strs = gMap(nums, func(elem int) string {
  167. return dict[elem]
  168. })
  169. print(strs) // 零 壹 贰 叁 肆 伍 陆 柒 捌 玖
  170. }
  171. func gReduce[T1 any, T2 any](arr []T1, init T2, f func(T2, T1) T2) T2 {
  172. result := init
  173. for _, elem := range arr {
  174. result = f(result, elem)
  175. }
  176. return result
  177. }
  178. func gReduceTest() {
  179. nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  180. sum := gReduce(nums, 0, func(result, elem int) int {
  181. return result + elem
  182. })
  183. fmt.Printf("Sum = %d \n", sum)
  184. }
  185. func gFilter[T any](arr []T, in bool, f func(T) bool) []T {
  186. result := []T{}
  187. for _, elem := range arr {
  188. choose := f(elem)
  189. if (in && choose) || (!in && !choose) {
  190. result = append(result, elem)
  191. }
  192. }
  193. return result
  194. }
  195. func gFilterIn[T any](arr []T, f func(T) bool) []T {
  196. return gFilter(arr, true, f)
  197. }
  198. func gFilterOut[T any](arr []T, f func(T) bool) []T {
  199. return gFilter(arr, false, f)
  200. }
  201. func gFilterTest() {
  202. nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  203. odds := gFilterIn(nums, func(elem int) bool {
  204. return elem%2 == 1
  205. })
  206. print(odds)
  207. }
  208. type Employee struct {
  209. Name string
  210. Age int
  211. Vacation int
  212. Salary float32
  213. }
  214. var employees = []Employee{
  215. {"Hao", 44, 0, 8000.5},
  216. {"Bob", 34, 10, 5000.5},
  217. {"Alice", 23, 5, 9000.0},
  218. {"Jack", 26, 0, 4000.0},
  219. {"Tom", 48, 9, 7500.75},
  220. {"Marry", 29, 0, 6000.0},
  221. {"Mike", 32, 8, 4000.3},
  222. }
  223. func gCountIf[T any](arr []T, f func(T) bool) int {
  224. cnt := 0
  225. for _, elem := range arr {
  226. if f(elem) {
  227. cnt += 1
  228. }
  229. }
  230. return cnt
  231. }
  232. type Sumable interface {
  233. type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64
  234. }
  235. func gSum[T any, U Sumable](arr []T, f func(T) U) U {
  236. var sum U
  237. for _, elem := range arr {
  238. sum += f(elem)
  239. }
  240. return sum
  241. }
  242. func main() {
  243. // stackTest() //stack 栈
  244. //listTest() //LinkList 双链表
  245. //gMapTest() //map
  246. //gReduceTest() //reduce
  247. //gFilterTest() //filter
  248. total_pay := gReduce(employees, 0.0, func(result float32, e Employee) float32 {
  249. return result + e.Salary
  250. })
  251. fmt.Printf("Total Salary: %0.2f\n", total_pay) // Total Salary: 43502.05
  252. //统计年龄大于40岁的员工数
  253. old := gCountIf(employees, func(e Employee) bool {
  254. return e.Age > 40
  255. })
  256. fmt.Printf("old people(>40): %d\n", old)
  257. // ld people(>40): 2
  258. //统计薪水超过 6000元的员工数
  259. high_pay := gCountIf(employees, func(e Employee) bool {
  260. return e.Salary >= 6000
  261. })
  262. fmt.Printf("High Salary people(>6k): %d\n", high_pay)
  263. //High Salary people(>6k): 4
  264. //统计年龄小于30岁的员工的薪水
  265. younger_pay := gSum(employees, func(e Employee) float32 {
  266. if e.Age < 30 {
  267. return e.Salary
  268. }
  269. return 0
  270. })
  271. fmt.Printf("Total Salary of Young People: %0.2f\n", younger_pay)
  272. //Total Salary of Young People: 19000.00
  273. //统计全员的休假天数
  274. total_vacation := gSum(employees, func(e Employee) int {
  275. return e.Vacation
  276. })
  277. fmt.Printf("Total Vacation: %d day(s)\n", total_vacation)
  278. //Total Vacation: 32 day(s)
  279. }

【转载】GO编程模式 : 泛型编程 - 图3 【转载】GO编程模式 : 泛型编程 - 图4
关注CoolShell微信公众账号和微信小程序
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
——=== 访问 酷壳404页面 寻找遗失儿童。 ===——

原文链接

https://coolshell.cn/articles/21615.html