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
200_success
146k22 gold badges190 silver badges478 bronze badges
asked May 29, 2016 at 0:12
\$\endgroup\$

1 Answer 1

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!

answered May 30, 2016 at 21:44
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.