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もないの楽しい