再帰的な書き方に頭が馴染んできた: Elixir

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、なるほどね。

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s