Help us understand the problem. What is going on with this article?

Elixir の Enum メモ

More than 3 years have passed since last update.

Elixir v1.1.1 Documentation を読めばよいのだけれど、な個人メモです。
*_while や *_by などの網羅はしてません。

Enum.empty?

空のリストだと true

[]
|> Enum.empty?
# true

nilだとエラー

iex(1)> Enum.empty?(nil)
** (Protocol.UndefinedError) protocol Enumerable not implemented for nil
    (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir) lib/enum.ex:112: Enumerable.reduce/3
    (elixir) lib/enum.ex:609: Enum.empty?/1

empty?がエラーになるくらいなので、Enumの関数は nilが引数に来たらだいたいエラー(多分)

Enum.concat

リストの結合
意外と存在を忘れがち

[1, 2, 3, 4, 5]
|> Enum.concat([6, 7, 8, 9, 10])
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

[1, 2, 3, 4, 5 | [6, 7, 8, 9, 10]] # 連結リストらしい結合
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

[1, 2, 3, 4, 5] ++ [6, 7, 8, 9, 10] # ++での結合
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

引数のどっちかが nilだと当然のようにエラー

iex(1)> [1, 2, 3, 4, 5] |> Enum.concat(nil)
** (Protocol.UndefinedError) protocol Enumerable not implemented for nil
    (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir) lib/enum.ex:112: Enumerable.reduce/3
    (elixir) lib/enum.ex:1398: Enum.reduce/3
    (elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:419: Enum.do_concat/1

Enum.into

リストの先頭に突っ込む ※リストの結合とは違う

[1, 2, 3, 4, 5]
|> Enum.into([11, 12, 13, 14, 15])
# [11, 12, 13, 14, 15, 1, 2, 3, 4, 5]

HashSetの先頭に突っ込んだりできる

[1, 2, 3, 4, 5, 2, 3, 4]
|> Enum.into(HashSet.new)
# #HashSet<[2, 3, 4, 1, 5]>
|> Enum.to_list
# [2, 3, 4, 1, 5]

[1, 2, 3, 4, 5, 2, 3, 4]
|> Enum.concat(HashSet.new)
# [1, 2, 3, 4, 5, 2, 3, 4]

※ HashSetは今後 MapSetを使うらしい

Enum.split, Enum.partition

条件に応じてリストを分割
使い道は意外とないかも

[1, 2, 3, 4, 5]
|> Enum.split(3)
# {[1, 2, 3], [4, 5]}

[1, 2, 3, 4, 5]
|> Enum.partition(fn(x) -> x==3 end)
# {[3], [1, 2, 4, 5]}

Enum.sum, Enum.count, Enum.min, Enum.max, Enum.min_max

リストの合計、長さ、最大、最小などなど
個人的にはなぜ Enumに定義されてるか謎

[1, 2, 3, 4, 5]
|> Enum.sum
# 15

[1, 2, 3, 4, 5]
|> Enum.min_by(fn(x) -> -x end)
# 5

sumは足せない要素があるとエラー

iex(1)> [{1}, 2, 3, 4] |> Enum.sum
** (ArithmeticError) bad argument in arithmetic expression
             :erlang.+({1}, 0)
    (elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3

Enum.join, Enum.intersperse

間になんか入れて返す

[1, 2, 3, 4, 5]
|> Enum.join(",")
# "1,2,3,4,5"

[1, 2, 3, 4, 5]
|> Enum.intersperse(",")
# [1, ",", 2, ",", 3, ",", 4, ",", 5]

Enum.map, Enum.map_join

mapは Linq(C#)の Selectみたいな感じで、リストの中身を変換できる
レコード数は変わらない
map_joinは mapして joinする(必要性はよくわからない)

[1, 2, 3, 4, 5]
|> Enum.map(fn(x) -> :math.pow(x, 2) end)
# [1.0, 4.0, 9.0, 16.0, 25.0]

Ectoの selectも似てる

MyApp.MyTable
|> select([m], %{"id" => m.id, "name" => m.name})
|> MyApp.Repo.all
# [%{"id" => 1, "name" => "foo"}, %{"id" => 2, "name" => "bar"}]

Enum.flat_map

mapした結果のリストを最後に concatする
mapと違ってレコード数を変えられるのがポイントのようだが有効な使いみちはよくわからない

[1, 2, 3, 4, 5]
|> Enum.map(fn(x) -> x*2 end)
# [2, 4, 6, 8, 10] # こっちは map

[1, 2, 3, 4, 5]
|> Enum.flat_map(fn(x) -> [x*2] end)
# [2, 4, 6, 8, 10] # fn内でリストを返している以外は↑と同じ

[1, 2, 3, 4, 5]
|> Enum.flat_map(fn(x) -> [:a, x*2] end)
# [:a, 2, :a, 4, :a, 6, :a, 8, :a, 10]

Enum.group_by, Enum.chunk, Enum.chunk_by

指定条件/要素数でグループ化する
group_byは Linqの GroupByと似た感じ

[11, 12, 13, 14, 15]
|> Enum.group_by(fn(x) -> rem(x, 2) end)
# %{0 => [14, 12], 1 => [15, 13, 11]}

[1, 2, 3, 4, 5]
|> Enum.chunk(2)
# [[1, 2], [3, 4]] # 割り切れなかった後ろの要素[5]は消える=子要素数確定

[1, 2, 3, 4, 5]
|> Enum.chunk(2, 1)
# [[1, 2], [2, 3], [3, 4], [4, 5]] # ステップ数指定

[1, 2, 3, 4, 5]
|> Enum.chunk(2, 3)
# [[1, 2], [4, 5]] # ステップ数指定

[1, 2, 3, 4, 5]
|> Enum.chunk_by(fn(x) -> x==3 end)
# [[1, 2], [3], [4, 5]]

Enum.all?, Enum.any?

リスト内の要素の全て/どれかが、指定した条件にマッチしているかどうか
フラグの判定で使ったりして見た目より応用が利く

[1, 2, 3, 4, 5]
|> Enum.all?(fn(x) -> x==3 end)
# false

[1, 2, 3, 4, 5]
|> Enum.any?(fn(x) -> x==3 end)
# true

Enum.filter, Enum.filter_map, Enum.reject, Enum.take, Enum.drop

指定した条件に応じて行を抽出/除外
filter_mapは filterして mapする(必要性はよくわからない)

[1, 2, 3, 4, 5]
|> Enum.filter(fn(x) -> x==3 end)
# [3]

[1, 2, 3, 4, 5]
|> Enum.reject(fn(x) -> x==3 end)
# [1, 2, 4, 5]

[1, 2, 3, 4, 5]
|> Enum.take(3)
# [1, 2, 3]

[1, 2, 3, 4, 5]
|> Enum.drop(3)
# [4, 5]

Enum.take_while, Enum.drop_while

条件を満たした要素を take/drop し続ける

[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
|> Enum.take_while(fn(x) -> x<=3 end)
# [1, 2, 3]

[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
|> Enum.drop_while(fn(x) -> x<=3 end)
# [4, 5, 1, 2, 3, 4, 5]

処理は(当然ながら)途中で打ち切られる

[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
|> Enum.take_while(fn(x) -> (x<=3) |> IO.inspect end)
# true
# true
# true
# false # falseになったので、それ以降の要素は処理せず棄てられる
# [1, 2, 3]

[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
|> Enum.drop_while(fn(x) -> (x<=3) |> IO.inspect end)
# true
# true
# true
# false # falseになったので、それ以降の要素は処理せず保持される
# [4, 5, 1, 2, 3, 4, 5]

Enum.take_every

一つ置きに処理したい時とか?
いまいち用途がわからない

[1, 2, 3, 4, 5]
|> Enum.take_every(2)
# [1, 3, 5]

Enum.each, Enum.with_index

while、forループの代替に使えるが、個人的には最初からそれを考えたら負けな気がする
breakで途中で止めるような処理がしにくいが、それは Enum.*_while が代替となるふいんき

[11, 12, 13, 14, 15]
|> Enum.with_index
# [{11, 0}, {12, 1}, {13, 2}, {14, 3}, {15, 4}]
|> Enum.each(fn(x) -> IO.inspect elem(x, 1) end)
# 0
# 1
# 2
# 3
# 4

Enum.random, Enum.take_random

リストからランダムに取得

[1, 2, 3, 4, 5]
|> Enum.random
# 4

[1, 2, 3, 4, 5]
|> Enum.take_random(3)
# [5, 1, 2]

Enum.sort, Enum.uniq, Enum.dedup, Enum.reverse, Enum.shuffle

並び替えとか
数値以外が混在してても動く

[{3}, 4, {5}, {1}, 2]
|> Enum.sort
# [2, 4, {1}, {3}, {5}]

[1, 2, 3, 4, 5]
|> Enum.uniq(fn(x) -> x==3 end)
# [1, 3]

Enum.slice, Enum.reverse_slice

sliceはリストの一部を切り取る
reverse_sliceはリストの一部を逆順にする ※つまりsliceではなく、reverseの仲間
個人的にはインデックスや長さを使ってリストをごにょごにょするのは elixir向きじゃない気がする

[1, 2, 3, 4, 5]
|> Enum.slice(1, 3)
# [2, 3, 4]

[1, 2, 3, 4, 5]
|> Enum.reverse_slice(1, 3)
# [1, 4, 3, 2, 5] # 範囲 1~3 が逆順になる

Enum.at, Enum.fetch!, Enum.find, Enum.find_index, Enum.find_value, Enum.member?

値が一致/条件に一致する要素やインデックス、真偽を返す
個人的にはインデックスや長さを使ってリストをご(略

[11, 12, 13, 14, 15]
|> Enum.at(2)
# 13 # 範囲外だと nil

[11, 12, 13, 14, 15, 13]
|> Enum.fetch!(2)
# 13 # 範囲外だと Enum.OutOfBoundsError

[11, 12, 13, 14, 15, 13]
Enum.find(fn(x) -> x==13 end)
# 13

[11, 12, 13, 14, 15, 13]
|> Enum.find_index(fn(x) -> x==13 end)
# 2

[11, 12, 13, 14, 15, 13]
|> Enum.find_value(fn(x) -> x==13 end)
# true

[11, 12, 13, 14, 15, 13]
|> Enum.member?(13)
# true # === で比較

Enum.reduce

リストを先頭から処理しながら折りたたむ(reduce)
前要素の処理結果が fnの第2引数になる ※なので、実際には2要素目から(length -1)回処理される

[1, 2, 3, 4, 5]
|> Enum.reduce(fn(x, acc) -> {x, acc} end)
# {5, {4, {3, {2, 1}}}}

[1, 2, 3, 4, 5]
|> Enum.reduce(fn(x, acc) -> x+acc end)
# 15

Enum.map_reduce

要素に対して処理していく
(elixir的な) mapして reduceするというわけではない模様

[1, 2, 3, 4, 5]
|> Enum.map_reduce(1, fn(x, acc) -> {x, acc+1} end)
{[1, 2, 3, 4, 5], 6} # mapで何もしない場合 初期値1に5回1を足して結果が6

[1, 2, 3, 4, 5]
|> Enum.map_reduce(1, fn(x, acc) -> {x*acc, acc+1} end)
{[1, 4, 9, 16, 25], 6}

Enum.scan

reduceと似てる ※ただし fnの第2引数は scanの引数で、1要素目から length回処理される
戻り値はリスト
使い道がやっぱり分からないが、コーディング時にすぐ思い至って使えるかどうかでコードの質が変わる(かも)。

[1, 2]
|> Enum.scan(99, fn(x, acc) -> {x, acc} end)
# [{1, 99}, {2, {1, 99}}]

[1, 2, 3, 4, 5]
|> Enum.scan(99, fn(x, acc) -> {x, acc} end)
# [{1, 99}, {2, {1, 99}}, {3, {2, {1, 99}}}, {4, {3, {2, {1, 99}}}}, {5, {4, {3, {2, {1, 99}}}}}]

[1, 2, 3, 4, 5]
|> List.foldl(99, fn(x, acc) -> {x, acc} end) # 最後の計算結果だけ取れる
# {5, {4, {3, {2, {1, 99}}}}}

まとめ

forも whileもないの楽しい

ohakado
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away