7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Elixirのチートシートを作ろう#7 enumその3

Last updated at Posted at 2024-12-11

Day7 Enum #3

1. Overview

さて、本日はEnumの以下のパッケージをまとめてしまおうと思います。
おまけで、キャプチャ演算子もやります。

関数名 説明
each すべての要素に対して、関数を適用する、が、戻り値は:OKである。
map すべての要素に対して、関数を適用する、が、戻り値はLISTである。
filter すべての要素に対して、関数を適用し、条件を満たすものをフィルタリングする。
reduce すべての要素に対して、関数を適用し、合計などを作成する。

今回、趣向を変えて、使い分けを意識して書いていきたいと思います。

2. まずは解説

2.1 each と map

まずはシンプルに、eachとmapの使い分けをしてみます。
どちらも、Listの要素全体を対象にします。

map:
iex(7)> Enum.map([0, 1, 2, 3], fn(x) -> x * 5 end)
[0, 5, 10, 15]
iex(8)>

listの各要素、0,1,2,3に対して、匿名関数fn(x) -> x * 5 endを適用し、その結果が
戻り値のLISTに入っているのがわかると思います。

0 x 5の結果, 1 x 5の結果, 2 x 5の結果, 3 x 5の結果
= [0, 5, 10, 15]
となります。

さて、次、each

each:

Enum.each(["one", "two", "three"], fn(s) -> IO.puts(s) end)
one
two
three
:ok

戻り値は:ok。
なにが重要かと言うと、fn(s) -> IO.puts(s) endで、IO.putsの様な外部に作用するコマンドを使っていること。
IO.puts()で、LISTの中の各要素が、出力されております。

では、mapの時に使ったLIST([0, 1, 2, 3])に対して、eachの時に使った匿名関数fn(s) -> IO.puts(s) endを
Enum.mapを使って各要素に実行してみましょう。

iex(6)> Enum.map([0, 1, 2, 3], fn(x) -> IO.puts(x) end)
0 ->こいつは、IO.puts(x)によって、出力されている。
1
2
3
[:ok, :ok, :ok, :ok]->これが、 Enum.mapの戻り値

mapは、各要素に関数を適用し、その戻り値をLISTへと格納する。
そして、今回の匿名変数fn(s) -> IO.puts(s) endは、画面にsを表示したら、成功(:ok)を返しています。
なので、mapの結果は、各要素のに対する実行結果(:ok)が、[:ok, :ok, :ok, :ok]と4つ入ったLISTが返っています。

eachの結果: :ok
mapの結果: [:ok, :ok, :ok, :ok] ※四回、fn(x) -> IO.puts(x) endが成功している。

戻り値が、違うことに注意してください。eachは何か関数を適用したい時、mapは、処理結果をLISTで欲しい時、
と言えそうですね。

などと解説してますが、ここまで文書を読んでもらっても、現時点では、eachのありがたみがわからないねぇ。
なので、次の章で組み合わせてみます。

2.2 filter

filterは、条件に合ったものを抽出します。条件は、関数で指定出来ます。

iex(8)> Enum.filter([1, 2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
[2, 4]

これは、引数である匿名関数fn(x) -> rem(x, 2) == 0 endの結果がtrueになるものだけが、
帰ってきてることがわかりますね。

xの値 実行される式 説明
1 rem( 1, 2)(*の結果は1) == 0 結果はfalse
2 rem( 2, 2)(*の結果は0) == 0 結果はtrue
3 rem( 3, 2)(*の結果は1) == 0 結果はfalse
4 rem( 4, 2)(*の結果は0) == 0 結果はtrue

匿名関数fn(x) -> rem(x, 2) == 0 endの結果が、trueになるのは、2と4の時だけなので、結果は[2,4]となっています。

さて、おまけでeachと組み合わせてみると…

iex(3)>  Enum.filter([1, 2, 3, 4], fn(x) -> rem(x, 2) == 0 end) |> Enum.each(fn(s) -> IO.puts("#{s}は2で割り切れます") end)
2は2で割り切れます
4は2で割り切れます
:ok

LIST[1, 2, 3, 4]から、Enum.filterで選択した結果[2,4]を、eachにて、画面に表示しております。
こういう時、戻り値がシンプルにok:ですので、便利ですな。

2.3 reduce

コレクションを1つの値に絞り込むことができます。絞り込むからreduceなんですね。
計算の絞りこむ計算の為のアキュムレータを提供します。

iex(3)> Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end)
6

変数、accが、引数となる関数(例では、fn(x, acc))に追加されております。

accに、どんどん加算…って式が加算している様には見えないですね。
これ、
x(これは引き渡された要素) + acc(それまでの値) をfn(x, acc) -> x + acc endが計算、戻り値(x+acc)が
新たにaccに入るって仕組みの様です。

iex(7)> Enum.reduce(["a", "b", "c"], fn(x, acc) -> x <> acc end)
"cba"
iex(8)> Enum.reduce(["a", "b", "c"], fn(x, acc) -> acc <> x end)
"abc"
iex(9)>

これ、listが、x <> accと連結されるんで、分かりやすいですかね?
要素の連結なんかにも使えます。

2.4 キャプチャ演算子(&)を使用したEnum

さて、ここでElixir Schoolを読んでみましょう。

'''
ElixirのEnumモジュール内の多くの関数は、渡されるEnum型のオブジェクトを処理するための引数として無名関数を取ります。
これらの無名関数は、多くの場合、キャプチャ演算子(&)を使用して省略形で記述されることが多いです。
Enumモジュールを使用してキャプチャ演算子を実装する方法を示すいくつかの例を次に示します。
各バージョンは機能的に同等です。
'''

…これ、例を見ていきましょう。まずは、スタンダード。

iex(9)> Enum.map([1,2,3], fn number -> number + 3 end)
[4, 5, 6]
iex(10)> Enum.map([1,2,3], fn (number) -> number + 3 end)
[4, 5, 6]

()を省略可能なのですが、ここまでfn (x)とやっていたので、両方例示しました。

では、キャプチャ演算子(&)の登場

iex(11)> Enum.map([1,2,3], &(&1 + 3))
[4, 5, 6]

fnがfn()に置き換わり、今までxと定義していた引数が、&1に変わりましたね。

今後、多用すると思いますが…本日はここまで。
名前付き関数でのキャプチャ演算子の使用、は、モジュールの説明後に送ります。
さすがに、今はおなか一杯。

3. 本日のチートシート!

Enumライブラリ(追加)

関数名 説明
all? コレクションの要素を一つずつ「評価」し、すべての要素がtrueなら、OKとする。 iex(120)> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 3 end)
false
iex(123)> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) > 4 end)
false
any? コレクションの要素を一つずつ「評価」し、いずれかの要素がtrueなら、OKとする。 iex(124)> Enum.any?(["foo", "bar", "hello"], fn(s) -> String.length(s) > 4 end)
true
chunk_every 要素を、引数2で指定した数で、分割します iex(4)> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
iex(5)> Enum.chunk_every([1, 2, 3, 4, 5, 6], 3)
[[1, 2, 3], [4, 5, 6]]
chunk_by 要素を、指定した関数で処理して分割する iex(9)> Enum.chunk_by(["one", "two", "three", "four", "five", "six"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"], ["six"]]
map_every すべての要素を、二つ目の引数で指定した数毎(nth)に関数(fun)で評価します 関数を最初に適用してから3つ毎に同様の処理
iex(20)> Enum.map_every([1, 2, 3, 4, 5, 6, 7, 8], 3, fn x -> x + 1000 end)
[1001, 2, 3, 1004, 5, 6, 1007, 8]
each すべての要素を指定した関数で評価し、評価出来たら:okを返す Enum.each(["one", "two", "three"], fn(s) -> IO.puts(s) end)
one
two
three
:ok
map すべての要素を指定した関数で評価し、結果をLISTで返す iex(7)> Enum.map([0, 1, 2, 3], fn(x) -> x * 5 end)
[0, 5, 10, 15]
filter すべての要素を指定した関数で評価し、関数の結果がtrueであるものだけを返す iex(8)> Enum.filter([1, 2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
[2, 4]
reduce コレクションを1つの値に絞り込む iex(3)> Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end)
6
min コレクションの最小値を返す iex(21)> Enum.min([1, 2, 3, 4, 5, 6, 7, 8])
1
max コレクションの最大値を返す iex(22)> Enum.max([1, 2, 3, 4, 5, 6, 7, 8])
8
min/2,max/2 コレクションが空であった場合にあらかじめ最小値を生成する為の関数を渡すことができます。 iex(23)> Enum.min([], fn -> :foo end)
:foo
sort コレクションをソートする Enum.sort([8, 2, 3, 4, 5, 6, 7, 1])
[1, 2, 3, 4, 5, 6, 7, 8]
sort_by 指定した関数で、sortを行う iex(138)> numlist = ["one", "two", "three", "four", "five", "six","nop"]
["one", "two", "three", "four", "five", "six", "nop"]
uniq 引き渡されたコレクションから、重複しているものを削除する iex(38)> Enum.uniq([1, 1, 2, 2, 3, 4, 5, 6, 7, 8])
[1, 2, 3, 4, 5, 6, 7, 8]
uniq_by 指定した関数で、重複を削除する iex(46)> Enum.uniq_by([%{x: 1, y: 1}, %{x: 2, y: 1}, %{x: 3, y: 3}], fn coord -> coord.y end)
[%{y: 1, x: 1}, %{y: 3, x: 3}]

reduceの説明が苦しいなぁ。をぃ。
では、また次回。

7
0
0

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
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?