5
\$\begingroup\$

I'm an elixir beginner. I'm doing the word count exercise of exercism website. The word count exercise returns a map with the word as the key and the number of instances as value. The exercise excludes special characters, and ignores underscores. My code works, but I'm sure there is a way to make it cleaner. I've attached the test file so that you can see the criteria that it needs to pass.

word_count.exs

defmodule Words do
  @doc """
  Count the number of words in the sentence.

  Words are compared case-insensitively.
  """
  @spec count(String.t) :: map
  def count(sentence) do
    String.downcase(sentence)                                           |>
      String.split(~r/[\s_]/)                                           |>
      Enum.filter(fn(word) -> String.match?(word, ~r/[a-zA-z\d]/) end)  |>
      Enum.map(fn(word) -> String.replace(word, ~r/[:!&@$%^,]/, "") end)|>
      Enum.group_by(fn(word) -> word end)                               |>
      Enum.reduce(%{}, fn({k, v}, acc) -> Map.put(acc, k, Enum.count(v)) end)
  end
end

word_count_test.exs

if !System.get_env("EXERCISM_TEST_EXAMPLES") do
  Code.load_file("word_count.exs")
end

ExUnit.start
ExUnit.configure exclude: :pending, trace: true

defmodule WordsTest do
  use ExUnit.Case

  test "count one word" do
    assert Words.count("word") == %{ "word" => 1 }
  end

  test "count one of each" do
    expected = %{ "one" => 1 ,  "of" => 1 ,  "each" => 1 }
    assert Words.count("one of each") == expected
  end

  test "count multiple occurrences" do
    expected = %{ "one" => 1 ,  "fish" => 4 ,  "two" => 1 ,  "red" => 1 ,  "blue" => 1 }
    assert Words.count("one fish two fish red fish blue fish") == expected
  end

  test "ignore punctuation" do
    expected = %{"car" => 1, "carpet" => 1, "as" => 1, "java" => 1, "javascript" => 1}
    assert Words.count("car : carpet as java : javascript!!&@$%^&") == expected
  end

  test "include numbers" do
    expected = %{"testing" => 2, "1" => 1, "2" => 1}
    assert Words.count("testing, 1, 2 testing") == expected
  end

  test "hyphens" do
    expected = %{"co-operative" => 1}
    assert Words.count("co-operative") == expected
  end

  test "ignore underscores" do
    expected = %{"two" => 1, "words" => 1}
    assert Words.count("two_words") == expected
  end

  test "normalize case" do
    expected = %{"go" => 3}
    assert Words.count("go Go GO") == expected
  end

  test "German" do
    expected = %{"götterfunken" => 1, "schöner" => 1, "freude" => 1}
    assert Words.count("Freude schöner Götterfunken") == expected
  end
end
\$\endgroup\$
2
\$\begingroup\$

Just finished that exercise too. I think you can extract some of that logic to anonymous functions. It isolates a pieces of behaviour that you can work on.

defmodule Words do
  @doc """
  Count the number of words in the sentence.

  Words are compared case-insensitively.
  """
  @spec count(String.t) :: map
  def count(sentence) do
    filter_word = fn(word) ->
      String.match?(word, ~r/[a-zA-z\d]/)
    end

    remove_special_characters = fn(word) ->
      String.replace(word, ~r/[:!&@$%^,]/, "")
    end

    count_words = fn({k, v}, acc) ->
      Map.put(acc, k, Enum.count(v))
    end

    sentence
    |> String.downcase
    |> String.split(~r/[\s_]/)
    |> Enum.filter(filter_word)
    |> Enum.map(remove_special_characters)
    |> Enum.group_by(fn(word) -> word end)
    |> Enum.reduce(%{}, count_words)
  end

end

And actually, I've created a function that returns a list of words to count: http://exercism.io/submissions/e26667aff3734da79ac0b6c6fedfd3da

Hope that helps!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.