標題:Map-Reduce-Filter 模式處理集合元素
日期:2022-07-04 10:37:00
toc:false
index_img:http://api.btstu.cn/sjbz/?lx=m_dongman&cid=9
類別:
- Go
標籤: - 用戶
- 遍歷
- 計算
- 統計
- 字串
- 函數
日常開發過程中,要處理陣列、片段、字典等集合類型,常規做法都是迴圈迭代進行處理。比如將一個字典類型用戶片段中的所有年齡屬性值提取出來,然後求和,常規實現是通過迴圈遍歷所有片段,然後從用戶字典鍵值對中提取出年齡欄位值,再依次進行累加。
針對簡單的單個場景,這麼實現沒什麼問題,但這是典型的面向過程思維,而且程式幾乎沒有什麼複用性可言:每次處理類似的問題都要編寫同樣的程式模板,比如計算其他欄位值,或者修改類型轉換邏輯,都要重新編寫實現程式。
在函數式編程中,我們可以通過 Map-Reduce
技術讓這個功能實現變得更優雅,程式複用性更好。
Map-Reduce 並不是一個整體,而是要分兩步實現:Map 和 Reduce,Map-Reduce 模型:先將字典類型片段轉化為一個字串類型片段(Map,字面意思就是一一映射),再將轉化後的片段元素轉化為整型後累加起來(Reduce,字面意思就是將多個集合元素通過迭代處理減少為一個)。
有的時候,為了讓 Map-Reduce 程式更加堅固(排除無效的欄位值),或者只對指定範圍的資料進行統計計算,還可以在 Map-Reduce 基礎上引入 Filter(過濾器),對集合元素進行過濾。
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)讓這個呼叫邏輯可讀性更好,更優雅。