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の説明が苦しいなぁ。をぃ。
では、また次回。