Map-Reduce-Filter 模式处理集合元素

日常开发过程中,要处理数组、切片、字典等集合类型,常规做法都是循环迭代进行处理。比如将一个字典类型用户切片中的所有年龄属性值提取出来,然后求和,常规实现是通过循环遍历所有切片,然后从用户字典键值对中提取出年龄字段值,再依次进行累加。

针对简单的单个场景,这么实现没什么问题,但这是典型的面向过程思维,而且代码几乎没有什么复用性可言:每次处理类似的问题都要编写同样的代码模板,比如计算其他字段值,或者修改类型转化逻辑,都要重新编写实现代码。

在函数式编程中,我们可以通过 Map-Reduce 技术让这个功能实现变得更优雅,代码复用性更好。

Map-Reduce 并不是一个整体,而是要分两步实现:Map 和 Reduce,Map-Reduce 模型:先将字典类型切片转化为一个字符串类型切片(Map,字面意思就是一一映射),再将转化后的切片元素转化为整型后累加起来(Reduce,字面意思就是将多个集合元素通过迭代处理减少为一个)。

有的时候,为了让 Map-Reduce 代码更加健壮(排除无效的字段值),或者只对指定范围的数据进行统计计算,还可以在 Map-Reduce 基础上引入 Filter(过滤器),对集合元素进行过滤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package main

import (
"fmt"
"strconv"
"time"
)

func main() {
var users = []map[string]string{
{
"name": "xmp",
"age": "11",
},
{
"name": "wlm",
"age": "24",
},
{
"name": "wjf",
"age": "30",
},
{
"name": "lm",
"age": "50",
},
}
fmt.Println("总年龄:", ageSum(users))

fmt.Println("总年龄:", handlerMapReduce(users))

fmt.Println("总年龄(过滤40以上和20岁以下):", handlerValidUsers(users))
}

func ageSum(users []map[string]string) int {
startTime := time.Now()
var result int
for _, user := range users {
age, _ := strconv.Atoi(user["age"])
result += age
}
fmt.Println("\tageSum 总耗时:", time.Since(startTime))
return result
}

func handlerValidUsers(users []map[string]string) int {
validUsers := filterMap(users, func(item map[string]string) bool {
ageStr, ok := item["age"]
if !ok {
return false
}
age, err := strconv.Atoi(ageStr)
if err != nil {
return false
}
if age > 40 || age < 20 {
return false
}
return true
})
return handlerMapReduce(validUsers)
}

func filterMap(items []map[string]string, f func(map[string]string) bool) []map[string]string {
startTime := time.Now()
var result []map[string]string
for _, item := range items {
if f(item) {
result = append(result, item)
}
}
fmt.Println("\tfilterMap 总耗时:", time.Since(startTime))
return result
}

func handlerMapReduce(users []map[string]string) int {
startTime := time.Now()
items := mapToString(users, func(user map[string]string) string {
return user["age"]
})
result := fieldSum(items, func(str string) int {
val, _ := strconv.Atoi(str)
return val
})
fmt.Println("\thandlerMapReduce 总耗时:", time.Since(startTime))
return result
}

func mapToString(items []map[string]string,
f func(map[string]string) string) []string {
newSlice := make([]string, len(items))
for _, item := range items {
newSlice = append(newSlice, f(item))
}
return newSlice
}

func fieldSum(items []string, f func(string) int) int {
var result int
for _, item := range items {
result += f(item)
}
return result
}

不过分开调用 Map、Reduce、Filter 函数不太优雅,我们可以通过装饰器模式将它们层层嵌套起来,或者通过管道模式(Pipeline)让这个调用逻辑可读性更好,更优雅。


Map-Reduce-Filter 模式处理集合元素
https://www.biuaxia.cn/2022/07/04/10/37/00.html
作者
biuaxia
发布于
2022年7月4日
许可协议