exercismでElixirの問題を解いていた時、いろいろなるほどなーと面白かったのでメモ。
仕様は、入力(sentence)に対して単語区切りごとに、sentence全体の文字をキーに含まれる文字をカウントする、というもの。
例えば、
“abc de fge”
を入力とすると、abc、de、fgeがそれぞれ1つづつ存在するとカウントする。
“abc de abc”
を入力とすると、abcが2つ、deが1つ存在するとカウントする、というもの。
Elixirだと、繰り返しは再帰的に書く。数列的な書き方になるので、少し頭になじませる時間が必要でしたが、再帰的な書き方に考えが馴染むと数学のようにかけて、個人的には書きやすかったです。プログラムだと手続き型に慣れていたけれど、関数的にかけると数学的な考え方をそのまま残せるのが良いですね。
defmodule Words do
@spec count(String.t) :: map()
def count(sentence) do
dict = %{}
String.downcase(sentence)
|> String.split(~r/[^\w-]|_/u)
|> count_up(dict)
end
defp count_up([head|tail], dict) do
if head != "" do
dict = Dict.update(dict, head, 1, fn x -> x + 1 end)
end
count_up(tail, dict)
end
defp count_up([], dict) do
dict
end
end
これを、さらにreduceを使って書いてみます。
defmodule Words do
@spec count(String.t) :: map()
def count(sentence) do
String.downcase(sentence)
|> String.split(~r/[^\w-]|_/u, trim: true)
|> Enum.reduce(%{}, &count_up/2)
end
defp count_up(head, dict) do
Dict.update(dict, head, 1, fn x -> x + 1 end)
end
end
Elixir1.0.4におけるreduceの説明は以下。
reduce(collection, fun) (function) # ↑ Specs: reduce(t, (element, any -> any)) :: any Invokes fun for each element in the collection passing that element and the accumulator acc as arguments. fun‘s return value is stored in acc. The first element of the collection is used as the initial value of acc. Returns the accumulator. Examples iex> Enum.reduce([1, 2, 3, 4], fn(x, acc) -> x * acc end) 24
Elixir、なるほどね。