I am attempting to solve a problem in Exercism “Protein Translation” and implemented the following code. I want the split(tail, final_list) to recursively add the 3 char to final_list. What am I doing wrong and how can I improve the code.
defmodule PT do
def rna(string) do
split(string, [])
end
def split(string, final_list) when string == "", do: final_list
def split(string, final_list) do
splitting = String.split_at(string, 3)
[head | tail] = Tuple.to_list(splitting)
final_list ++ [head]
split(tail, final_list)
end
end
i am getting this error:
iex(21)> PT.rna("Owaissdui")
** (FunctionClauseError) no function clause matching in String.Unicode.next_grapheme_size/1
The following arguments were given to String.Unicode.next_grapheme_size/1:
# 1
["issdui"]
Attempted function clauses (showing 10 out of 19263):
def next_grapheme_size(<<13::integer(), 10::integer(), rest::binary()>>)
def next_grapheme_size(<<"\r"::binary(), rest::binary()>>)
def next_grapheme_size(<<"\n"::binary(), rest::binary()>>)
def next_grapheme_size(<<""::binary(), rest::binary()>>)
def next_grapheme_size(<<""::binary(), rest::binary()>>)
def next_grapheme_size(<<""::binary(), rest::binary()>>)
def next_grapheme_size(<<""::binary(), rest::binary()>>)
def next_grapheme_size(<<""::binary(), rest::binary()>>)
def next_grapheme_size(<<""::binary(), rest::binary()>>)
def next_grapheme_size(<<""::binary(), rest::binary()>>)
...
(19253 clauses not shown)
You are getting the error because tail is a list, but you are passing it to split/2 which expects the first argument to be a string. split/2 takes tail and tries to call String.split/2, which then throws an error because it expects its first argument to be a string, not a list.
If you simply want to split a string into lists of three characters, I can think of ways that are simpler, but using this approach I would suggest:
def split("", final_list), do: Enum.reverse(final_list)
def split(string, final_list) do
{first_three_characters, rest_of_word} = String.split_at(string, 3)
split(rest_of_word, [first_three_characters | final_list])
end
Wont it just return the value as we that in elixir the last expression of a function gets returned automatically. I might be wrong, can you kindly advice ?
you are absolutely right and it’s such a dumb mistake to make. A very solid way of using pattern matching. One question, why you have used a tuple and why not a list
String.split/2 returns a tuple, so only a tuple will match on the result returned by that function.
I am prepending the string to an accumulated list of strings. This is a common practice as prepending to a list is more efficient than appending to one. That is also why Enum.reverse/1 is called at the end, because prepending ends up generating a list of items in reverse that needs to be reversed again to preserve the original order.
In this case, the last expression in your function is
split(tail, final_list)
which calls split/2 using the same value for final_list that was passed to the function originally. You have already discussed that tail is causing a type error in this case, but regardless, the result of your list concatenation operation is simply thrown away.