title: Map-Reduce-Filter パターンでコレクション要素を処理する
date: 2022-07-04 10:37:00
toc: false
index_img: http://api.btstu.cn/sjbz/?lx=m_dongman&cid=9
category:
- Go
tags: - ユーザー
- 反復処理
- 計算
- 統計
- 文字列
- 関数
日常の開発プロセスでは、配列、スライス、マップなどのコレクション型を処理する必要があります。通常の方法は、ループを使用して反復処理を行うことです。たとえば、辞書型のユーザースライスからすべての年齢属性値を抽出して合計する場合、通常の実装ではスライス全体をループし、ユーザーの辞書のキーと値から年齢フィールド値を抽出し、順番に合計します。
単一のシンプルなシナリオに対しては、この方法で問題ありませんが、これは典型的な手続き型の考え方であり、コードの再利用性はほとんどありません。同様の問題を処理するたびに同じコードテンプレートを書く必要があります。たとえば、他のフィールド値を計算するか、型変換ロジックを変更する場合、実装コードを再度書く必要があります。
関数型プログラミングでは、Map-Reduce
技術を使用してこの機能をより優雅に実装し、コードの再利用性を向上させることができます。
Map-Reduce は単一のエンティティではなく、2 つのステップで実装されます:Map と Reduce。Map-Reduce モデルでは、まず辞書型スライスを文字列型スライスに変換します(Map、文字通りの意味での一対一のマッピング)、次に変換後のスライス要素を整数型に変換して合計します(Reduce、複数のコレクション要素を反復処理して 1 つに減らすことを意味します)。
場合によっては、Map-Reduce コードをより堅牢にするために(無効なフィールド値を除外する)、または特定の範囲のデータのみを統計計算するために、Map-Reduce にフィルタ(フィルタリング)を導入することもできます。
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)を使用して呼び出しロジックをより読みやすく、より優雅にすることができます。