# Count the number of non-numeric characters in a string and then sum their values

Hello.
Trying to solve this problem I wrote the following algorithm:

``````string = "3340c9571y40m47ku49t9315mrvzqo667k36e"

# %{
#  "c" => 1,
#  "e" => 1,
#  "k" => 2,
#  "m" => 2,
#  "o" => 1,
#  "q" => 1,
#  "r" => 1,
#  "t" => 1,
#  "u" => 1,
#  "v" => 1,
#  "y" => 1,
#  "z" => 1
#  }
#  [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1]
# Expected result => 14

def is_numeric?(char) do
Regex.match?(~r/^\d+\$/, char)
end

def total_non_numeric_chars(string) do
String.codepoints(string)
|> Enum.reduce(%{}, fn char, acc ->
cond do
!is_numeric?(char) ->
if Map.has_key?(acc, char),
do: Map.update!(acc, char, &(&1 + 1)),
else: Map.put(acc, char, 1)

true ->
acc
end
end)
|> Map.values()
|> Enum.sum()
end
``````

I would like to know if is possible to avoid the nested cond and if with some patter matching or other technique.
Any suggestions to improve the algorithm is welcome.

Thanks.

``````if !is_numeric?(char) do
Map.update(acc, char, 1, &(&1 + 1))
else
acc
end
``````
1 Like

Map.update/4 Thanks so much!

Just be aware of the fact that `is_numeric?` is an unidiomatic name for a function, either we use the prefix `is_` when we have guardsave predicates or we use the suffix `?` when the predicate is not guardsafe.

3 Likes

Hi @NobbZ
Thank you for your clarification.
So the proper name would be:

``````def numeric?(char) do
Regex.match?(~r/^\d+\$/, char)
end
``````

Correct?

Since you’re summing them up at the end, I guess you don’t really need to keep the frequency map - you can just keep the sum (unless you also output the map somewhere).

With that in mind, this is how I’d write it:

``````def total_non_numeric_chars(string), do: loop(string, 0)

defp loop(<<char, rest::binary>>, count) when char in ?0..?9, do: loop(rest, count)
defp loop(<<_, rest::binary>>, count), do: loop(rest, count + 1)
defp loop(<<>>, count), do: count
``````
4 Likes

Caveat:

``````iex(1)> String.codepoints "noe\u0308l"
["n", "o", "e", "̈", "l"]
iex(2)> String.graphemes "noe\u0308l"
["n", "o", "ë", "l"]
iex(3)>
``````
1 Like

Hi @michalmuskala
This is a very interesting approach.
Thanks so much for sharing it. I just thought of using the new `reduce` comprehensions. With that it can be a one-liner:

``````for << <<char>> <- string >>, char not in ?0..?9, reduce: 0, do: (acc -> acc + 1)
``````
5 Likes

Hello @peerreynders.
Thank you very much for pointing this caveat. That is very impressive.

Could you share a link that can take me to the “new reduce comprehensions” documentation?

Thank you very much

1 Like
3 Likes

Thank you very much!

It could be a one-liner even without new fancy `reduce` comprehension option ``````length(for << char <- string >>, char not in ?0..?9, do: char)
# or
byte_size(for << char <- string >>, char not in ?0..?9, into: "", do: <<char>>)
``````
2 Likes