12
2

More than 1 year has passed since last update.

Enum.reduceのデバッグにEnum.scanが便利

Last updated at Posted at 2021-12-15

この記事は「fukuoka.ex Elixir/Phoenix Advent Calendar 2021」の16日目です。
昨日は@MzRyuKaさんの「elixir-desktopを起動しようとして環境構築でハマっている話」でした。

導入

さて、本日は「Enum.reduceのデバッグにEnum.scanが便利」というテーマです。
Enumにはmapfilterreduceといった、使用率の高い非常に便利な関数がありますが、reducemapfilterとは少し異なります。mapは受け取ったリストと同じサイズのリストを返し、filterは受け取ったリストのサイズ以下のリストを返します。

# Enum.map
lst = [1,2,3,4,5]
Enum.map(lst, fn n -> n * 2 end)
# [2,4,6,8,10]

# Enum.filter
Enum.filter(lst, fn n -> n > 3 end)
# [4,5]

両者に共通していることは必ずリストを返すという点です。しかし、reduceに関しては必ずしもリストを返さない関数です。

  • リストの要素の合計値を求める
  • リストの重複する要素を取り除く
  • 出現する要素を集計する...

などなど、「リストから何かをする」というケースに非常に便利に用いることが出来ます。

lst = [1,2,3,4,5]
Enum.reduce(lst, 0, fn n, accum -> accum + n end)
# 15

reduceの難しい点🤔

便利なreduce関数ですが、データの動き方には少し慣れが必要です。
基本的にはアキュムレーターと呼ばれる値の更新処理が重要となります。

リストから要素の合計を求めるという処理の場合は、初期値を0の数値をアキュムレーターとして、数値同士の加算処理を行う必要があります。

fn n, accum -> accum + n end

次のアキュムレーターがどのような値になっているかを把握するのがreduceの難点です。
普通にデバッグをすると以下のようになります(実際に過去に自分はこの方法でやっていました)。

lst = [1,2,3,4,5]
Enum.reduce(lst, 0, fn n, accum ->
  next = accum + n
  IO.inspect(next)
  next
end)

# ※見やすさのために改行しています
# 1 3 6 10 15

第2引数の無名関数内部で、標準出力を行えばいいのですが、これでは非常に面倒です...

Enum.scanを使う🧪

Enumで提供されているscanがreduceのデバッグに非常に便利です。
まずはEnum.scanの実行結果を見てみます。

lst = [1,2,3,4,5]
Enum.scan(lst, 0, fn n, accum -> accum + n end)
# [1,3,6,10,15]

どうでしょうか。先ほどのアキュムレーターの値の更新結果を確認することが出来ました。
リストの最大値を取得する処理でも見てみます。

lst = [3,1,4,5,2]
Enum.scan(lst, 0, fn n, acc ->
  if n > acc do
    n
  else
    acc
  end
end)
# [3,3,4,5,5]

最大値がどのように推移しているのかが確認できました。reduceの動作確認には非常に便利ですね。
ぜひご活用ください。

注意点としては、Enum.scanEnum.reduceと異なり、最終的にはリストを返します。なので、デバッグが完了したら、必ずreduceに戻しておく必要があります。

最後に

明日は「fukuoka.ex Elixir/Phoenix Advent Calendar 2021」の17日目は@pojiroさんです。
お楽しみに🙌

12
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
2