banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

Map-Reduce-Filter パターンでコレクション要素を処理する

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)を使用して呼び出しロジックをより読みやすく、より優雅にすることができます。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。