田舎で並行処理の夢を見る

試していることなど...需要がないかもしれないけど細々とアウトプットしてます

優しく分かるEnumのreduce関数と簡単なサンプル

reduce関数の動き方

reduce関数好きですか?僕は嫌いです。最近は好きです
javascriptにもpython(要import)あるreduce関数なんですけど
使い道があんまり思いつかなかった頃がありました
map関数とかfilter関数は名前の通りで使いやすいんですけど
reduce関数に関しては「え、どこで使うんですか?」ってレベルで理解がなかった(クソザコ

ただ勘違いしてはいけないのはreduce関数を絶対に使わなければいけないわけじゃないってことです
reduce関数を使うことで少ないコード量でタスクをこなせる場合がある程度なので
共通することですが、100%理解する必要はないと思ってます(無責任
使ってる内に覚えるでしょの精神で生きてます

それでもreduce関数が知りたいという物好きな方は以下をどうぞ

前提知識のアキュムレーター(accumulator)について

reduce関数がよう分からん理由って動き方がきもいからです
ちょっと関数型言語の知識が必要で 、値がどんな感じで渡されていくのかが
オブジェクト言語だけの知識だと本当に意味不明...
理由は簡単でアキュムレーター(accumulator)たるものを知らないからです

アキュムレーター(accumulator)
意味: 蓄積者、蓄財家、蓄電池、累算器

ようは値を保存してくれる銀行のようなものです(??
また別記事に書きますがreduce関数の裏では再帰関数が動いてます
再帰関数では終了条件に一致しない場合に自分自身(関数)を呼び出します(再帰)
そうするとコードは上から処理されていくので当然ながら
1つ前の再帰関数内で算出した値は上書きされていまいます(同じ関数を処理するため)
再帰関数ではないですけどpythonでの例を挙げるとすればこうかな

python

def sample():
  lst_data = [1,2,3,4,5,6,7,8]
  for num in lst_data:
    sum_num = 0
    sum_num += num
  
  return sum_num
  
print(sample()) #result: 8 (思ってた結果と違う... 1~8の合計値が欲しいのに)
#リストの合計がしっかり...しっかり求まる処理
def sample():
  lst_data = [1,2,3,4,5,6,7,8]
  sum_num = 0
  for num in lst_data:
    sum_num += num
  
  return sum_num
  
print(sample()) #result: 36 いいね。

こういう感じの値確保は関数型言語だとこうなる
elixir

defmodule Sample do
  def sum(lst_data) do
    _sum(lst_data, 0) #この0がアキュムレーターの初期値
  end
  defp _sum([], accum), do: accum
  defp _sum([head | tail], accum) do
    _sum(tail, head + accum) #ここが重要で下コメントにて動き方
  end
end

#1回目の再帰: head = 1, tail = [2,3,4,5,6,7,8]
#関数の引数に算出値を加える([2,3,4,5,6,7,8], 1 + 0)

#2回目の再帰: head = 2, tail = [3,4,5,6,7,8]
#関数の引数に算出値を加える([3,4,5,6,7,8], 2 + 1 + 0)

#3回目の再帰: head = 3, tail = [4,5,6,7,8]
#関数の引数に算出値を加える([4,5,6,7,8], 3 + 2 + 1 + 0)
:
:
#8回目の再帰: head = 8, tail = []
#関数の引数に算出値を加える([], 8 + 7 + .... 3 + 2 + 1 + 0)
# 第1引数のリストが空になったので終了。第2引数accumをreturn

res = Sample.sum([1,2,3,4,5,6,7,8])
IO.puts(res) #result: 36 いいね。

関数型言語の場合にはこんな感じで関数の引数に前の結果を投げてあげて
次の再帰処理に値を渡すことが可能になります
これが貯めるというニュアンスだからアキュムレータってことですかと

reduce関数の場合のアキュムレーター

先ほどの例では関数を関数でラップすることで
アキュムレーターの初期値として「0」を与えました
賢い方はお気づきかと思いますけど(悔スィ
reduceは再帰関数だけどアキュムレーターもってんの?
という話になるかと思います
答えはyesです

reduce関数の場合はenumerables(列挙可能)の先頭の値になります

#こいつの場合は1が初期値
_lst_data = [1,2,3,4....100]

#こいつの場合は"apple"が初期値
_lst_data = ["apple", "banana", "peach"]

reduce関数のまとめ
先頭の値を初期値として保存して残りの要素で何かやろうよっていう関数

日本語下手くそですんません
とはいいつつ、elixirのEnumには3つの引数を取るreduce関数も用意されています
この関数では任意の初期値を与えることが可能です

reduce関数を使ったサンプル

じゃあ何ができるかという話になるので3つぐらいサンプル置いときます

リストの要素の合計を求める(Enum.sum使おうぜ)

lst_data = [1,2,3,4,5,6,7,8]
cal_res = Enum.reduce(lst_data, fn next, prev -> next + prev end)

#value(先ほどの再帰関数処理と全く同じ)
# 1回目: next: 2, prev = 1
# 2回目: next: 3, prev = 1 + 2
# 3回目: next: 4, prev = 1 + 2 + 3
:
:

IO.puts(cal_res) #result: 36

リストの要素から最大値を探す

lst_data = [5,2,12,4,22,6,7,8]
cal_res = Enum.reduce(lst_data, fn next, prev ->
            if next > prev do
              next
            else
              prev
            end
          end)

IO.puts(cal_res) #result: 22

最小値を探す場合にはifの条件を逆転するだけ

リストの次元を1つ落とす

lst_data = [
  [1,2,3],
  [4,5,6],
  [7,8,9]
]

res = Enum.reduce(lst_data, [], fn data, accum -> accum ++ data end)
IO.inspect(res)
#result: [1, 2, 3, 4, 5, 6, 7, 8, 9]

Enum.flatten()だと予期せぬ動作をする時があってこれはたまに使います

まとめ

reduce関数は使いどころが難しい...
ハマると強いが、だいたいのことは他の関数が用意されているのでそっち使えばいいじゃんってなる
こういう使い方もあるぜってご存知の方がいれば教えてください

また何か思いついたらサンプルに加えておきます
頭使ったので疲れました