晴耕雨読

working in the fields on fine days and reading books on rainy days

[Elixir] 配列の要素数をカウントする

Elixirで配列の要素数をカウントする方法について、単語の出現回数を数える例を使って説明します。

目標

例えば、下のような文字列の配列があるとき、

words = [
  "And", "on", "the", "seventh", "day", "God", "finished", "the", "work",
  "that", "he", "had", "done", ",", "and", "he", "rested", "on", "the",
  "seventh", "day", "from", "all", "the", "work", "that", "he", "had",
  "done", "."]

その要素(単語)の出現回数をカウントしたいとします。 期待する出力は Map で、key はその要素、value は出現回数とします。

result = %{
  "," => 1, "." => 1, "And" => 1, "God" => 1, "all" => 1, "and" => 1,
  "day" => 2, "done" => 2, "finished" => 1, "from" => 1, "had" => 2, "he" => 3,
  "on" => 2, "rested" => 1, "seventh" => 2, "that" => 2, "the" => 4,
  "work" => 2}

解決方法

はじめに結論を言うと、次のコードを使えば良いです。

Enum.reduce(list, %{}, fn x, acc -> Map.update(acc, x, 1, &(&1 + 1)) end)

Enum.reduce(enumerable, acc, fun)

まず、Enum.reduce を使って一つのオブジェクトにまとめていきます。 Map にまとめていきたいので、acc の初期値は %{} もしくは Map.new とします。

次に、fun には、1つのオブジェクトにまとめる方法を書きます。 fun には各要素と acc (初期値は Map.new) がパラメータとして渡されるので、ここでは Map.update を使って acc を更新していきます。

Map.update(map, key, initial, fun)

Map.update は渡された mapkeyinitial を代入しますが、すでに値が入っていた場合には fun を実行してその返り値を代入する関数です。

上のコードの場合だと、map に未登録の単語があれば 1 で初期化し、すでに単語が存在すれば &(&1 + 1) をします。 つまり、現在の値に 1 を加えます。

結論

最終的に単語を数えるコードは次のようになります。

words |> Enum.reduce(%{}, fn word, acc -> Map.update(acc, word, 1, &(&1 + 1)) end)