Enum

この和訳は最新です。

コレクションを列挙していくために用いる一連のアルゴリズム。

目次

Enum

Enumモジュールはおよそ70個以上の関数を含んでいます。前回のレッスンで学習した、タブルを除外した全てのコレクションは全て列挙可能です。

このレッスンは利用可能な関数のうち一部分しか取り上げませんが、実は全ての関数を自分自身で調べることができます。 IExでちょっとした実験をしてみましょう。

iex
iex> Enum.__info__(:functions) |> Enum.each(fn({function, arity}) ->
...>   IO.puts "#{function}/#{arity}"
...> end)
all?/1
all?/2
any?/1
any?/2
at/2
at/3
...

これを見ればEnumモジュールに大量の機能があるのが明らかで、これには明確な理由があります。 列挙は関数型プログラミングの核で、信じられないほど有用です。 おまけに列挙は、以前見たような言語レベルでサポートされているドキュメントといった、Elixirの他の要素と共に活用することで、信じられないような効果をもたらします。

全ての関数を知りたい場合は公式ドキュメントのEnumを参照してください。尚、列挙の遅延処理ではStreamモジュールを利用してください。

all?

all?を使うとき、そしてEnumの多くのケースで、コレクションの要素に適用する関数を渡します。all?の場合には、コレクション全体でこの関数はtrueと評価されなければならず、これを満たさない場合はfalseが返ります。

iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 3 end)
false
iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) > 1 end)
true

any?

上記と違って、any?は少なくとも1つの要素がtrueと評価された場合にtrueを返します:

iex> Enum.any?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 5 end)
true

chunk_every

コレクションを小さなグループに分割する必要があるなら、恐らくchunk_every/2こそが探し求めている関数でしょう:

iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]

chunk_every/4にはいくつかのオプションがありますが、ここでは触れないのでこの関数の公式ドキュメントを調べてください。

chunk_by

コレクションを要素数ではない何か他のものでグループにする必要がある場合には、chunk_by/2関数を使うことができます。この関数は列挙可能な値と関数を引数に取り、その関数の返り値が変わると新しいグループが始まります:

iex> Enum.chunk_by(["one", "two", "three", "four", "five"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"]]
iex> Enum.chunk_by(["one", "two", "three", "four", "five", "six"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"], ["six"]]

map_every

時にコレクションをグループに別けるだけでは充分ではない場合があります。nth毎のアイテムに対して何かの処理をしたい時にはmap_every/3が有用です。これは最初の要素にかならず触れます。

# 毎回3個を飛び越えながら関数を呼び出す
iex> 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

新しい値を生成することなく、コレクションを反復する必要があるかもしれません。こうした場合にはeach/2を使います:

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

注記: each関数は:okというアトムを返します。

map

関数を各要素に適用して新しいコレクションを生み出すには、map関数に目を向けましょう:

iex> Enum.map([0, 1, 2, 3], fn(x) -> x - 1 end)
[-1, 0, 1, 2]

min

コレクションの中で最小の(min/1)値を探します:

iex> Enum.min([5, 3, 0, -1])
-1

min/2も同様ですが、コレクションが空である場合、最小値を生成するための関数を渡します:

iex> Enum.min([], fn -> :foo end)
:foo

max

コレクションの中で最大の(max/1)値を返します:

iex> Enum.max([5, 3, 0, -1])
5

max/2max/1の関係はmin/2min/1の関係と同じです:

Enum.max([], fn -> :bar end)
:bar

filter

filter/2を使用するとコレクションで与えられた関数で評価して true になる要素のみを返すことができます。

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

reduce

reduce/3を用いることで、コレクションをまとめ、そこから単一の値を抽出することができます。この処理を実行するにはオプションとしてアキュムレータ(積算器。この例では10)を関数に渡しますが、アキュムレータが与えられない場合にはコレクションの最初の値が用いられます:

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

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

iex> Enum.reduce(["a","b","c"], "1", fn(x,acc)-> x <> acc end)
"cba1"

sort

コレクションをソートするのは1つではなく、2つあるソート関数を使えば簡単です。

sort/1はErlangのterm orderingを使ってソート順序を決めるというものです:

iex> Enum.sort([5, 6, 1, 3, -1, 4])
[-1, 1, 3, 4, 5, 6]

iex> Enum.sort([:foo, "bar", Enum, -1, 4])
[-1, 4, Enum, :foo, "bar"]

その反面、sort/2には順序決めに使う関数を渡すことができます:

# ソート関数あり
iex> Enum.sort([%{:val => 4}, %{:val => 1}], fn(x, y) -> x[:val] > y[:val] end)
[%{val: 4}, %{val: 1}]

# なし
iex> Enum.sort([%{:count => 4}, %{:count => 1}])
[%{count: 1}, %{count: 4}]

uniq_by

uniq_by/2を使ってコレクションから重複した要素を取り除くことができます:

iex> Enum.uniq_by([1, 2, 3, 2, 1, 1, 1, 1, 1], fn x -> x end)
[1, 2, 3]